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

SystemAdapterPurpose
Azure Entra IDEntraIdClientAuthentication
Alpiq EBSadapter/alpiq/Energy Balance System
Alpiq MDRadapter/alpiq/Market Data Repository
Alpiq OPLadapter/alpiq/Operations Planning
Volue EMSadapter/volue/Energy Management System
Volue ATPadapter/volue/Token provider
Fingridadapter/fingrid/Finnish market data

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.

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