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), MSRV rust-version = 1.88.0, edition 2021 + Python 3.12 via PyO3
  • Version: 1.16.0 (origin/develop @ 92bfe1c8, 2026-07-09)
  • Main branch: develop — local develop fast-forwarded 2026-07-13 and now tracks origin/develop; still fetch + diff against origin/develop before trusting local state

Master conventions guide

An untracked AGENTS.md at the mando repo root is the canonical conventions document (non-negotiables, style, error system, recipes, landmines). Fully rebuilt 2026-07-13 against the develop tip 92bfe1c8 — 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 on develop, code wins.

Workspace Crates

Workspace members on origin/develop @ 92bfe1c8, 2026-07-09 (dependency direction: mando-coremando-lib → binaries; never invert):

CrateTypePurpose
mando-corelibNo internal deps: error! macro, DataPointId, model primitives, DataFrame validation. Compiled into the Python extension — keep lightweight
mando-liblibData layer: repos (PG/DuckDB/SQLite/passthrough), MandoService, adapters (Alpiq/Volue/Fingrid), workflow engine, validation
mando-codegenbuild-depBuild-time generator: YAML + Askama templates → Rust (not a proc macro); output under mando-bess/build/generated/ is tracked in git
mando-flow-steplibFlowStepService 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-deriveproc-macroParamEnum + 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-liblibShared mando-bess library (split out in the Jul-08 restructure). Zero tests
mando-bessbinREST API (Axum 0.8.7, port 8080): routes, flow execution, scheduling, auth (JWT/Entra ID)
py-mandocdylibPyO3 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-simulationcdylibPyO3 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-simulatorbinFlow-simulation server (uses position-manager)
position-managerlibMarket position bookkeeping

Adjacent (not workspace members):

CrateTypePurpose
mando-scrtbinRust-based Python runtime: virtual module injection, Arrow FFI streaming, memory-safe algo hosting (PyO3 auto-initialize)
mando-clibinWorkspace 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-ambinBESS 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-macro does not exist on develop

The #[derive(ErrorCode)] crate previously listed here lives only in .worktrees/ experiments (poc-error-extractor, BE-2023, BE-1595/BE-3482). error.kind/error.code are extracted at runtime by mando_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)

FlagEnables
appAxum + OpenTelemetry (used by mando-bess)
codegenCode generation with CSV
pythonPyO3 classes/functions
workflowFlow 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-type src/flow/{type}/v{n}/ dirs to flow_registry.rs in the Jul-08 restructure (with new crates mando-flow-step + mando-flow-step-derive); steps register via 36 type_entry! entries in mando-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 existing TimeSeriesDouble/TimeSeriesDoubleMatrix/StaticData. Full reference: BESS AM (BE-2262) - mando-bess-am.

  • Headless Kinesis stream processor (mando-bess-am crate) 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 prefix BESS_AM_*; Kinesis adapter at mando-lib/src/adapter/kinesis/; Event model at mando-core/src/model/{event,wago}.rs; Event datapoints in mando-bess/config/parts/battery_online.yaml.
  • Status (2026-06-24): core MR !512 OPEN, blocked on review (requested_changes); nothing merged to develop. 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 datapointSystemPurposeProtocolAuthEnv prefix
Maintenance/FI/VolueEmsVolue EMS (Energy Management System)Spot data, timeseries read/write, AS bidding, schedulingRESTCustom token endpoint (user/pass + separate web-service creds), 55 min refreshVOLUE_EMS_*
Maintenance/FI/VolueAtpVolue ATP (Algo Trading Platform)Order books, trading statistics, parameter templates (EPEX + Nord Pool exchanges)RESTOAuth2 client credentials, 60 min refreshVOLUE_ATP_*
Maintenance/FI/MetisMetis (Market Data Platform)Timeseries retrievalRESTAPI key or Entra ID (METIS_ENTRA_ID_AUTH toggle)METIS_*
Maintenance/FI/MetisGraphQLMetis GraphQL (Merit Order / Events)Merit orders, multi-resolution, eventsGraphQLAPI key or Entra IDMETIS_GRAPHQL_*
Maintenance/FI/FingridFingrid (Finnish TSO Open Data)Reserve capacity, balancing market datasetsREST (public + frontend API)API key rotation (comma-separated list, round-robin)FINGRID_*
Maintenance/FI/PositionManagerPosition ManagerPositions, market states, FCR/aFRR results, energy tradesGraphQL + WebSocket subscriptionsEntra IDPOSITION_MANAGER_*
Maintenance/FI/EBSEBS (European Bidding System / EPEX)DA bid file upload, remove open DA ordersSMB3 file drop (.xlsx)SMB user/pass + workgroupEBS_SMB_*
Maintenance/FI/OPLOPL (Order Placement Layer / Nordpool IDC), aka LikronIDC order create/remove, strategiesRESTEntra IDOPL_*
Maintenance/FI/MDRMDR (Master Data Repository)Document / static-data retrievalRESTOnePassport / Entra IDMDR_*
Maintenance/FI/KinesisAWS Kinesis (Real-Time Event Stream)BESS AM event ingest (WAGO to IoT Core to Kinesis)AWS SDK streamAWS credential chainBESS_AM_*
Maintenance/FI/MandoAlgoForecastMando Algo (Price Forecast)Price forecast runsRESTnone (internal)FORECAST_ALGO_*
Maintenance/FI/MandoAlgoOptimizationMando Algo (Optimization)Optimization runsRESTnone (internal)OPTIMIZATION_ALGO_*
Maintenance/FI/DataPlatformData Platform (AWS Athena / Fingrid Backup)Fingrid data mirror queriesAWS Athena SDKAWS credential chainDATA_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 = stringCount
Volue EMS12
Position Manager4
Metis4
Optimization Algo3
OPL (Likron)2
MS Teams & E-Mail2
EBS2
Volue1
MDR1
Mando1
Likron1
Forecast Algo1
Fingrid1

Likron = OPL

Confirmed by system = "OPL (Likron)" at mando-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.rs declares system = "Likron" while it is implemented against Volue ATP; as_auction_update/energy_bids.rs declares a bare "Volue". Because the strings are free-form and inconsistent, telemetry aggregation by flow.step.system is 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.

StyleAdapters
config crate / parse_config_with_prefixOPL (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

LayerTechnology
WebAxum 0.8.7, Tower middleware
DataPolars 0.49.1, Arrow 56.2.0
DBPostgreSQL (deadpool), DuckDB 1.4.2, SQLite (rusqlite)
AuthJWT (RS256), Entra ID, Volue EMS/ATP
PythonPyO3 0.25.1, pyo3-polars 0.22.0, Maturin 1.9.2
ObservabilityOpenTelemetry 0.30.0 (OTLP/gRPC), tracing
API docsutoipa + Scalar UI
GraphQLcynic 3.12.0 + graphql-ws-client
Serializationserde, serde_json, serde_yaml, Apache Avro
Error handlingthiserror 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

EndpointMethodPurpose
/query_dataRead data points
/save_dataWrite data points
/flow/{id}Manage flows
/flow_executionsList executions
/flow_executions/{asset_id}Asset executions
/audit_logsAudit trail
/settingsConfiguration CRUD
/versionsComponent 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 extension

Never run cargo fmt in ANY form

Bare cargo fmt reformats ~64 legacy files, and cargo fmt -- path/to/file.rs does NOT scope either (field-verified 2026-07-13: it touched api_docs.rs, which was never named; see BE-3482 Datadog Logs and APM Conformance). Format single files with rustfmt --edition 2021 path/to/file.rs. The import-grouping/comment options in rustfmt.toml are 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=1 so 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 as rtk proxy cargo test --all-features --release -- --test-threads=1 and 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 grep and git log pipelines reported zero matches for content that was demonstrably present. An rtk-mediated NEGATIVE result is never evidence of absence. The only trustworthy check is rtk 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-slim with 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