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.
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/
AppSpecat 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
anyhowat 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
utilmodules. 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, notOpnsenseDns;SecretVault, notOpenbaoStore. Test: if you can swap the backend without rewriting any Score, the boundary is right. Exception: when the developer codes to the implementation —PostgreSQL, notDatabase, 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 outsideScore::interpret. CLIs, examples, and test harnesses compose*Scoretypes. Building aDeployment/Service/ConfigMapin 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
Topologychanges. 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 (
MyAppScorepulls inPostgresScore, …). No monolithic "deploy everything" Scores. - Deploy returns only after smoke-test success.
helm install && hopeis the anti-pattern Harmony exists to kill. Convergence errors read likerustc, not "exit 1 from helm". (Contract shape open; principle locked.) - Deploy logic lives in a
*-deploycrate depending on bothharmonyand the runtime crate. Runtime binaries stay free of theharmonydep. 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/InterpretAPI stays small. thiserrorin libraries;anyhowonly 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.