Agent Context — Alpiq BESS (Mando)
Purpose
Dense reference for AI agents to quickly gather project context. Load this file first when working on any Mando crate.
Identity
- Project: Battery Energy Storage System optimization platform
- Org: Alpiq, Sales & Origination / Flexible Assets
- Repo:
sales-and-origination/flexible-assets/bess/poc/mando(GitLab) - Source path:
/Volumes/bandi/coding/poc/mando(workspace root:/Volumes/bandi/coding/poc/) - Language: Rust toolchain 1.89.0 (
rust-toolchain.toml), MSRVrust-version = 1.88.0, edition 2021 + Python 3.12 via PyO3 - Version: 1.16.0 (
origin/develop@92bfe1c8, 2026-07-09) - Main branch:
develop— localdevelopfast-forwarded 2026-07-13 and now tracksorigin/develop; still fetch + diff againstorigin/developbefore trusting local state
Master conventions guide
An untracked
AGENTS.mdat the mando repo root is the canonical conventions document (non-negotiables, style, error system, recipes, landmines). Fully rebuilt 2026-07-13 against thedeveloptip92bfe1c8— it no longer lags the tip. See Mando AGENTS.md Master Guide for its map, the round-1 corrections, and the round-2 verified facts. When it disagrees with code ondevelop, code wins.
Workspace Crates
Workspace members on origin/develop @ 92bfe1c8, 2026-07-09 (dependency direction: mando-core → mando-lib → binaries; never invert):
| Crate | Type | Purpose |
|---|---|---|
mando-core | lib | No internal deps: error! macro, DataPointId, model primitives, DataFrame validation. Compiled into the Python extension — keep lightweight |
mando-lib | lib | Data layer: repos (PG/DuckDB/SQLite/passthrough), MandoService, adapters (Alpiq/Volue/Fingrid), workflow engine, validation |
mando-codegen | build-dep | Build-time generator: YAML + Askama templates → Rust (not a proc macro); output under mando-bess/build/generated/ is tracked in git |
mando-flow-step | lib | FlowStepService trait — a construction trait (NOT execute-style): type Params: ParamMeta; type Response; + async from_config(config: &str, providers: &StepProviders); StepProviders (src/providers.rs) carries flow_repository, data_point_registry, 6 auth providers, simulation_enabled. Zero tests |
mando-flow-step-derive | proc-macro | ParamEnum + ParamMeta derives for flow-step params (not ErrorCode — no such derive exists on develop); param structs derive #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ParamMeta)]. Zero tests |
mando-bess-lib | lib | Shared mando-bess library (split out in the Jul-08 restructure). Zero tests |
mando-bess | bin | REST API (Axum 0.8.7, port 8080): routes, flow execution, scheduling, auth (JWT/Entra ID) |
py-mando | cdylib | PyO3 extension (abi3-py38): algo params, DataFrame ops, algo runner HTTP server, adapters for Python. SimulationRunner REMOVED (moved to py-mando-simulation, commit 3559042d — breaking) |
py-mando-simulation | cdylib | PyO3 bindings for the simulator — py_mando.SimulationRunner lives HERE now; requires Python ≥3.11, ships ddtrace, no polars/pandas. Rust side zero tests, 1 Python integration test |
mando-simulator | bin | Flow-simulation server (uses position-manager) |
position-manager | lib | Market position bookkeeping |
Adjacent (not workspace members):
| Crate | Type | Purpose |
|---|---|---|
mando-scrt | bin | Rust-based Python runtime: virtual module injection, Arrow FFI streaming, memory-safe algo hosting (PyO3 auto-initialize) |
mando-cli | bin | Workspace CLI: typed commands (init/pull/build/up/down/override/config/mock/migrate/volume/release), git handler with SSH/HTTPS fallback, Docker lifecycle via bollard, config resolution (standalone repo at /Volumes/bandi/coding/poc/mando-cli/) |
mando-bess-am | bin | BESS AM (BE-2262, in flight) — headless Kinesis stream processor for WAGO battery telemetry; consumes bess-am-events, 1-min aggregates, forwards to bess-os-events. See BESS AM (BE-2262) - mando-bess-am. NOT yet merged to develop. |
mando-lib-macrodoes not exist ondevelopThe
#[derive(ErrorCode)]crate previously listed here lives only in.worktrees/experiments (poc-error-extractor, BE-2023, BE-1595/BE-3482).error.kind/error.codeare extracted at runtime bymando_core::error!. See Mando AGENTS.md Master Guide.
Key Abstractions
MandoService (async trait)
├── retrieve / retrieve_at / retrieve_history — read data points
├── insert — write data points
├── retrieve_catalog — list available data points
└── send — push data externally
Repository (trait) → DataPointRepository, CacheRepository
├── RepositoryPostgres (production)
├── RepositoryDuckDb (analytics/OLAP)
├── RepositorySqlite (local dev)
└── RepositoryPassthrough (caching layer)
DataPointId = hierarchical path: "Asset/FI/Battery/SoC" → ["Asset","FI","Battery","SoC"]
DataPointType = TimeSeriesDouble | TimeSeriesDoubleMatrix | StaticData
DataPointFilterQueryMode = ORIGINAL | OVERRIDE | MERGED
Feature Flags (mando-lib)
| Flag | Enables |
|---|---|
app | Axum + OpenTelemetry (used by mando-bess) |
codegen | Code generation with CSV |
python | PyO3 classes/functions |
workflow | Flow execution engine |
Flow Engine
Versioned workflows for BESS trading strategies. On origin/develop @ 2026-07-08 (mando-bess/config/flows/fi/ + manifest.yaml):
- Manual Schedule (v3) — operator schedules; the canonical exemplar flow
- Auction (v4) — auction bidding
- AS Auction Update (v2) — ancillary-services auction updates
- Intraday (v2) — intraday ops
- Data Update (v2) — data pipelines
Released flow versions are immutable
Never change a released vN’s YAML or generated code — breaking behavior goes to v(N+1). Definition YAMLs generate Rust via
build.rs(tracked in git: commit YAML + regenerated output together). Src-side wiring moved from per-typesrc/flow/{type}/v{n}/dirs toflow_registry.rsin the Jul-08 restructure (with new cratesmando-flow-step+mando-flow-step-derive); steps register via 36type_entry!entries inmando-bess/build.rs.
config/flows/manifest.yaml is the deployment catalog: setup: {version}: flows: {name}: {path, schedule_env, semaphore_group} — scheduling env vars and concurrency groups are configured in the manifest, not in code; semaphores have capacity 1 per group.
BESS AM (BE-2262) — real-time telemetry workstream
Separate from the flow engine
BESS AM = BESS Asset Management is a newer workstream, NOT one of the flows above. It introduces a new
Event-typed data-point class (real-time telemetry) alongside the existingTimeSeriesDouble/TimeSeriesDoubleMatrix/StaticData. Full reference: BESS AM (BE-2262) - mando-bess-am.
- Headless Kinesis stream processor (
mando-bess-amcrate) for per-second WAGO battery telemetry (real-time SoC/SoH) on the FI/Valkeakoski/Beskar asset. - Pipeline:
WAGO Box → AWS IoT Core → Kinesis (bess-am-events) → mando-bess-am → Kinesis (bess-os-events) → mando-bess. - Persists raw events, computes 1-min aggregates (Mean/Max/Min/Last/StdDev/Count/Sum) via windowing + grace-period closure, forwards to
bess-os-events. - Postgres schema
bess_am; env prefixBESS_AM_*; Kinesis adapter atmando-lib/src/adapter/kinesis/; Event model atmando-core/src/model/{event,wago}.rs; Event datapoints inmando-bess/config/parts/battery_online.yaml. - Status (2026-06-24): core MR !512 OPEN, blocked on review (
requested_changes); nothing merged todevelop. Builds on BE-2132 (interval/Event groundwork), depends on BE-2341 (generic Kinesis producer, folded into !512).
External Integrations
Rebuilt 2026-08-13 against branch poc/e2e-tests (the previous 7-row table was badly incomplete). Roughly 15 external systems plus 6 auth providers. The maintenance-datapoint column is the Maintenance/{country}/{Service} path used by the planned outage gate (External Service Outage Gate - bitmask design).
| Maintenance datapoint | System | Purpose | Protocol | Auth | Env prefix |
|---|---|---|---|---|---|
Maintenance/FI/VolueEms | Volue EMS (Energy Management System) | Spot data, timeseries read/write, AS bidding, scheduling | REST | Custom token endpoint (user/pass + separate web-service creds), 55 min refresh | VOLUE_EMS_* |
Maintenance/FI/VolueAtp | Volue ATP (Algo Trading Platform) | Order books, trading statistics, parameter templates (EPEX + Nord Pool exchanges) | REST | OAuth2 client credentials, 60 min refresh | VOLUE_ATP_* |
Maintenance/FI/Metis | Metis (Market Data Platform) | Timeseries retrieval | REST | API key or Entra ID (METIS_ENTRA_ID_AUTH toggle) | METIS_* |
Maintenance/FI/MetisGraphQL | Metis GraphQL (Merit Order / Events) | Merit orders, multi-resolution, events | GraphQL | API key or Entra ID | METIS_GRAPHQL_* |
Maintenance/FI/Fingrid | Fingrid (Finnish TSO Open Data) | Reserve capacity, balancing market datasets | REST (public + frontend API) | API key rotation (comma-separated list, round-robin) | FINGRID_* |
Maintenance/FI/PositionManager | Position Manager | Positions, market states, FCR/aFRR results, energy trades | GraphQL + WebSocket subscriptions | Entra ID | POSITION_MANAGER_* |
Maintenance/FI/EBS | EBS (European Bidding System / EPEX) | DA bid file upload, remove open DA orders | SMB3 file drop (.xlsx) | SMB user/pass + workgroup | EBS_SMB_* |
Maintenance/FI/OPL | OPL (Order Placement Layer / Nordpool IDC), aka Likron | IDC order create/remove, strategies | REST | Entra ID | OPL_* |
Maintenance/FI/MDR | MDR (Master Data Repository) | Document / static-data retrieval | REST | OnePassport / Entra ID | MDR_* |
Maintenance/FI/Kinesis | AWS Kinesis (Real-Time Event Stream) | BESS AM event ingest (WAGO to IoT Core to Kinesis) | AWS SDK stream | AWS credential chain | BESS_AM_* |
Maintenance/FI/MandoAlgoForecast | Mando Algo (Price Forecast) | Price forecast runs | REST | none (internal) | FORECAST_ALGO_* |
Maintenance/FI/MandoAlgoOptimization | Mando Algo (Optimization) | Optimization runs | REST | none (internal) | OPTIMIZATION_ALGO_* |
Maintenance/FI/DataPlatform | Data Platform (AWS Athena / Fingrid Backup) | Fingrid data mirror queries | AWS Athena SDK | AWS credential chain | DATA_PLATFORM_* |
Present in code but without a maintenance datapoint yet:
- ET-3000 (
ET_3000_*, basic auth, volume profiles) - MS Teams incoming webhook + MS Graph e-mail (error notification)
- OnePassport (
ONE_PASSPORT_*) - S3 archiver
- mando REST / simulator self-clients
- mandarrow-client (Arrow Flight gRPC)
Authoritative in-code registry
The source of truth for “which system does this step talk to” is the system = "..." attribute on #[step(...)] in mando-flow-step/src/service/ and mando-bess-lib/src/service/. Verified declaration counts on poc/e2e-tests:
system = string | Count |
|---|---|
| Volue EMS | 12 |
| Position Manager | 4 |
| Metis | 4 |
| Optimization Algo | 3 |
| OPL (Likron) | 2 |
| MS Teams & E-Mail | 2 |
| EBS | 2 |
| Volue | 1 |
| MDR | 1 |
| Mando | 1 |
| Likron | 1 |
| Forecast Algo | 1 |
| Fingrid | 1 |
Likron = OPL
Confirmed by
system = "OPL (Likron)"atmando-bess-lib/src/service/intraday/idc_order.rs:122. Likron is the vendor/product behind the OPL order placement layer for Nordpool intraday continuous. Treat the two names as one system.
System strings are NOT normalized
mando-flow-step/src/service/volue_atp_order_book.rsdeclaressystem = "Likron"while it is implemented against Volue ATP;as_auction_update/energy_bids.rsdeclares a bare"Volue". Because the strings are free-form and inconsistent, telemetry aggregation byflow.step.systemis broken: the same physical system appears under several labels and one label points at the wrong system. Normalize (ideally to an enum) before relying on this dimension in dashboards or on the outage gate.
Auth providers
Six auth providers hang off StepProviders (mando-flow-step/src/providers.rs:15-20): metis_auth, volue_ems_auth, volue_atp_auth, position_manager_auth, one_passport_auth, entra_id_auth.
Config-loading split
Newer adapters use parse_config_with_prefix / the config crate; older ones still use #[derive(Envconfig)]. Reviewers are steering new configuration toward the config crate.
| Style | Adapters |
|---|---|
config crate / parse_config_with_prefix | OPL (mando-lib/src/adapter/alpiq/opl.rs:162), Volue ATP (volue/atp/atp_auth_provider.rs:45), Fingrid (fingrid/config.rs:32), Entra ID |
#[derive(Envconfig)] | EBS, MDR, Metis, ET-3000, Volue EMS, Position Manager, OnePassport |
Crates on poc/e2e-tests missing from older crate tables
mando-macros, mando-bess-am, mando-repository.
Integration-relevant workspace dependencies
aws-sdk-athena, aws-sdk-s3, aws-sdk-kinesis, cynic + graphql-ws-client (Metis GraphQL + Position Manager subscriptions), smb 0.11.2 (EBS), scraper, tonic (Arrow Flight), OTLP/OpenTelemetry.
Tech Stack Summary
| Layer | Technology |
|---|---|
| Web | Axum 0.8.7, Tower middleware |
| Data | Polars 0.49.1, Arrow 56.2.0 |
| DB | PostgreSQL (deadpool), DuckDB 1.4.2, SQLite (rusqlite) |
| Auth | JWT (RS256), Entra ID, Volue EMS/ATP |
| Python | PyO3 0.25.1, pyo3-polars 0.22.0, Maturin 1.9.2 |
| Observability | OpenTelemetry 0.30.0 (OTLP/gRPC), tracing |
| API docs | utoipa + Scalar UI |
| GraphQL | cynic 3.12.0 + graphql-ws-client |
| Serialization | serde, serde_json, serde_yaml, Apache Avro |
| Error handling | thiserror enums + mando_core::error! macro (runtime error.kind/error.code extraction; tracing::error! is clippy-banned; no ErrorCode derive on develop). Redesign landed in stages: ae6d1098 (error.message = root cause, error.stack = source chain, fingerprint) then MR !569 (fingerprint = {code}|{step_path}|{activity}) and MR !571 = BE-3541 Single Error Emission (logged_at_site/logged() REMOVED: construct ErrorWithStepStatus with new/message only, never log at the detection site; one boundary emission carries HttpContext). error.trace + .step_context() NOT landed. MR !578 = BE-3656 APM Span Enrichment MERGED 2026-07-22 (merge 4a0d297f = develop tip: error.*/http.* on trace spans). In flight: BE-3657 error_stack Adoption (full-path codes via error_stack 0.8, rebased onto 4a0d297f, 14 commits @ ab8cfd11 incl. the DD_SERVICE env read fix, MR pending on Andras’s yes) |
mando-bess Routes
| Endpoint | Method | Purpose |
|---|---|---|
/query_data | — | Read data points |
/save_data | — | Write data points |
/flow/{id} | — | Manage flows |
/flow_executions | — | List executions |
/flow_executions/{asset_id} | — | Asset executions |
/audit_logs | — | Audit trail |
/settings | — | Configuration CRUD |
/versions | — | Component versions |
Build Commands
cargo build # Build workspace
cargo test --all-features --release -- --test-threads=1 # CI test command; single-threaded MANDATORY (shared in-memory DB pools)
cargo clippy --release --all-features # the lint gate (CI has NO fmt gate)
cargo run -p mando_bess # Run service (port 8080)
cd py-mando && maturin develop # Build Python extensionNever run
cargo fmtin ANY formBare
cargo fmtreformats ~64 legacy files, andcargo fmt -- path/to/file.rsdoes NOT scope either (field-verified 2026-07-13: it touchedapi_docs.rs, which was never named; see BE-3482 Datadog Logs and APM Conformance). Format single files withrustfmt --edition 2021 path/to/file.rs. The import-grouping/comment options inrustfmt.tomlare nightly-only and silently ignored on the stable 1.89 toolchain; import grouping is maintained by hand, and the tip tree is NOT fmt-clean.format!on tip is ~3:1 positional over inline; inline args are the review rule for new code.
RTK machines: the shell hook falsifies the test gate
The RTK hook rewrites
cargo test --all-features --release -- --test-threads=1so the threads flag becomes a test-name filter: every test is filtered out, exit 0 in 0.00s (signature:0 passed, N filtered out); two gate runs were false-green this way before detection (2026-07-13). Run it asrtk proxy cargo test --all-features --release -- --test-threads=1and confirm the summed passed totals are nonzero; exit 0 alone proves nothing. Piped forms report the LAST command’s exit (cargo test | grep | tail; echo $?is tail’s status). See BE-3482 Datadog Logs and APM Conformance.
RTK machines: grep/log pipelines return FABRICATED zeros (2026-08-05)
While verifying a git history rewrite, rtk-wrapped
grepandgit logpipelines reported zero matches for content that was demonstrably present. An rtk-mediated NEGATIVE result is never evidence of absence. The only trustworthy check isrtk proxy git ...redirected to a file, then reading the file. See mando-repos-history-rewrite-2026-08-05.
CI/CD
- Platform: GitLab CI
- Stages: Security (Snyk) → Container → Setup → Build/Test → Publish → Release → Pages
- Branching:
feature/*,bugfix/*,develop→ Dev ECR;release/*→ Prod ECR - Runtime image:
debian:13.1-slimwith ca-certificates, libc6, libssl3, bundled libduckdb.so (samba libs removed by the remotefs-smb to smb migration; build image still installs them) - Gates: clippy (
--release --all-features) + tests (--test-threads=1); no fmt gate - New jobs (Jul 2026): “PyMando Simulation Linux Dev” (pytest, without
--nbval) + a simulator docker publish child pipeline - Repo hygiene: no MR/issue templates, no CODEOWNERS
File Layout
mando/
├── mando-core/src/ # error.rs (error! macro), model/, util/, validation/, python/
├── mando-lib/src/ # adapter/, audit/, bess/, model/, util/, workflow/, repo_*.rs, ...
├── mando-bess/src/ # flow/ (flow_registry.rs on tip), route/, database/, model/
├── mando-bess/build/generated/ # GENERATED Rust (domain.rs + flows) — tracked in git, never hand-edit
├── py-mando/src/ # 12 modules: algo, polars, adapter, data_point, ...
├── assets/ # Test data
├── lib/libduckdb/ # Bundled DuckDB 1.4.2 (git LFS)
├── scripts/ # Benchmarks, load tests, seed data
└── test/ # Manual E2E scripts