Files
harmony/AGENTS.md
Jean-Gabriel Gill-Couture 224d60907d
All checks were successful
Run Check Script / check (pull_request) Successful in 2m20s
docs(agents): lead with the minimalism / DRY / anti-bloat bar
Rewrite AGENTS.md to open with a blunt,
non-negotiable engineering bar: match the implementation to the real
complexity of the problem, DRY in the purest sense (one authoritative
representation; the trust-boundary discriminator for what is and isn't
duplication), no speculative abstraction (Rule of Three / YAGNI), no
ceremony, core logic in one cohesive home (no leaking into external
modules), WHY-only comments. Applies to all agent output — code, tests,
docs, commits, replies. Reference sections compressed; 171 -> fewer
lines, strictly more pointed.
2026-05-29 09:25:47 -04:00

8.1 KiB

AGENTS.md

The engineering contract for this repo. Read it before any change.

The bar: minimal, DRY, no bloat — non-negotiable

Match the implementation to the real complexity of the problem, and stop there. The shortest correct solution wins. If 200 lines do a 20-line job, it's wrong — rewrite it. A senior calling your code "overcomplicated" is a failed review.

  • DRY in the purest sense. Every piece of knowledge has exactly one authoritative representation. The same condition written twice — even in unrelated parts of the system — is a defect. The discriminator: the same check at two trust boundaries (untrusted frontend + trusted backend) is not duplication — those are distinct axes of the problem. The same check inside one trust domain (app layer + DB layer, both trusted) is. Always ask: does this axis exist in the problem, or only in my solution?
  • No speculative abstraction. Solve the case in front of you, not a hypothetical future. Introduce an abstraction at the second real instance, not the first (Rule of Three). A struct/trait/enum/AppSpec at n=1 that is longer than what it replaces is a defect. YAGNI.
  • No ceremony. No struct where a field works; no trait where a function works; no error enum where anyhow at the binary edge works; no test that exists only to exercise a helper you added to enable that test; no config knob nothing sets; no job/feature wired for an axis that doesn't exist yet.
  • Core logic stays in one cohesive home. Never leak or scatter a concern across helpers, wrappers, and util modules. Don't reimplement in a test harness, CLI, or example what a Score (or any owning module) already does — compose it.
  • Comments: WHY only. A non-obvious constraint, an invariant, a workaround. Never narrate WHAT the code does. If deleting a comment loses nothing, it was bloat. No multi-paragraph docstrings.
  • This governs everything an agent emits — code, tests, docs, commit messages, and chat replies alike. Concise, information-dense, unbloated. Here brevity is correctness, not style.

Formal frame: minimize accidental complexity (Brooks); the floor is the problem's intrinsic (Kolmogorov) complexity. Over-duplication and over-abstraction are the same error — a description longer than the problem warrants.

What Harmony is

Orchestration for decentralized micro datacenters — small clusters in homes, offices, and community spaces instead of hyperscalers. It replaces the Terraform/Ansible/Helm "YAML mud pit" (config validated at runtime, fragmented across tools, failing at 3 AM) with one Rust codebase where the compiler catches infrastructure misconfigurations before anything deploys. Infrastructure-as-real-code, not a wrapper around existing tools.

Score-Topology-Interpret — the core pattern

  • Score — declarative desired state; a struct generic over T: Topology. Serializable, cloneable, idempotent.
  • Topology — what an environment can do; exposes capabilities as traits (DnsServer, K8sclient, HelmCommand, LoadBalancer, …). E.g. K8sAnywhereTopology (local k3d or any cluster), HAClusterTopology (bare-metal HA).
  • Interpret — translates a Score into operations against a Topology's capabilities; returns an Outcome (SUCCESS, NOOP, FAILURE, RUNNING, QUEUED, BLOCKED).

Compile-time safety through trait bounds:

impl<T: Topology + DnsServer + DhcpServer> Score<T> for DnsScore { ... }

A Topology missing a required capability is a compile error, not a runtime surprise. Higher-order topologies compose via blanket impls: if T: PostgreSQL then FailoverTopology<T>: PostgreSQL automatically — zero boilerplate.

Capability & Score rules (non-negotiable)

  • Capabilities are industry concepts, not tools. DnsServer, not OpnsenseDns; SecretVault, not OpenbaoStore. Test: if you can swap the backend without rewriting any Score, the boundary is right. Exception: when the developer codes to the implementation — PostgreSQL, not Database, because you write PG-specific SQL.
  • Scores encapsulate operational complexity. Init sequences, retries, distro quirks live inside the Score. A high-level example is ~15 lines, not ~400 of imperative orchestration.
  • Idempotent and order-independent. Twice = once. Declare needs via trait bounds; never assume another Score ran first.

See docs/guides/writing-a-score.md.

Deploy architecture (ADR-023, non-negotiable)

  • Deploy with Scores, not handrolled manifests. No k8s_openapi::api::* structs outside Score::interpret. CLIs, examples, and test harnesses compose *Score types. Building a Deployment/Service/ConfigMap in a harness is the mud pit in Rust clothing — reach for the Score, or write the missing one.
  • E2E uses the same Scores as prod. Only the Topology changes. If e2e needs something prod doesn't, add a knob to the Score — don't fork the manifest.
  • One Score per deployable component; compose upward (MyAppScore pulls in PostgresScore, …). No monolithic "deploy everything" Scores.
  • Deploy returns only after smoke-test success. helm install && hope is the anti-pattern Harmony exists to kill. Convergence errors read like rustc, not "exit 1 from helm". (Contract shape open; principle locked.)
  • Deploy logic lives in a *-deploy crate depending on both harmony and the runtime crate. Runtime binaries stay free of the harmony dep. One deploy crate per app area.
  • Topologies are compile-time, selected at runtime. A deploy binary lists its topologies; a new backend is a rebuild. No Box<dyn Topology> loaders.
  • Extend Scores with companions, not API changes. New cross-cutting capabilities (dry-run, observability, smoke-test) wrap a Score; the base Score/Interpret API stays small.
  • thiserror in libraries; anyhow only at binary glue (main.rs-level, where the error is just printed).

See docs/adr/023-deploy-architecture.md.

Architecture (hexagonal, ADR-002)

harmony/src/domain/ (score.rs, topology/, interpret/, inventory/, maestro/) is the framework core; infra/ (opnsense, brocade, kube, sqlx) holds driven adapters; modules/ (k8s, postgresql, okd, helm, monitoring, kvm, network, …) holds concrete deployment modules. The domain stays isolated from adapters.

Key crates

Crate Purpose
harmony Core framework: domain, infra adapters, deployment modules
harmony_cli CLI + optional TUI (--features tui)
harmony_config Unified config+secret management (env → SQLite → OpenBao → prompt)
harmony_secret / _derive Secret backends (LocalFile, OpenBao, Infisical)
harmony_execution Execution engine
harmony_agent / harmony_inventory_agent Persistent agent framework (NATS JetStream mesh), hardware discovery
harmony_assets Asset management (URLs, local cache, S3)
harmony_composer Infrastructure composition tool
harmony-k8s Kubernetes utilities
k3d Local K3D cluster management
brocade Brocade switch integration
opnsense-codegen / opnsense-api Typed OPNsense client (XML models → Rust); exist because OPNsense ships no typed API. Support crates, not core.

Key ADRs (docs/adr/)

001 Rust · 002 hexagonal · 003 capabilities at domain level (no vendor lock-in) · 005 Rust DSL over YAML · 007 k3d default runtime · 009 Helm inflated to vanilla k8s YAML · 015 higher-order topologies via blanket impls · 016 agent mesh on NATS JetStream · 020 unified config+secret · 023 deploy architecture.

Build & test

./build/check.sh                                   # full CI: check + fmt + clippy + test
cargo check --all-targets --all-features --keep-going
cargo fmt --check
cargo clippy
cargo test                                         # or: cargo test -p <crate> <test>
cargo run -p <example_crate>
mdbook build

Conventions

Rust edition 2024, resolver v2. Conventional commits (feat:/fix:/chore:/docs:/refactor:). Small, single-purpose PRs (~200 lines, excluding generated code). License AGPL v3. Lean on the type system — compile-time guarantees over runtime checks; abstractions domain-level, not provider-specific.