From 1ba435e0ec0f9343a3fcc5f432369290d73015a6 Mon Sep 17 00:00:00 2001 From: hartsock Date: Wed, 9 Sep 2026 07:30:44 -0400 Subject: [PATCH] docs: AGENTS.md with ponytail + the UNIX philosophy as design doctrine WHAT: renames CLAUDE.md to AGENTS.md, symlinks CLAUDE.md back to it (the convention in herdr/ and the workspace root), and adds a Design doctrine section that makes ponytail and the UNIX philosophy the standard a change is judged against here. Also corrects the crate listing, which said five crates when there are ten. WHY: this project is a monitoring daemon, and the two philosophies answer its two recurring questions -- ponytail how much code should exist, UNIX what shape it takes. One file rather than two that drift. Four UNIX rules decide most arguments in a daemon like this one, so they are named rather than left implied. The Rule of Silence is first: a monitor that chatters trains its operator to ignore it, and an ignored monitor is worse than none because it also consumes the belief that something is watching. Composition means alerts are structured events another tool can read, not prose in a dashboard. Separation keeps thresholds in config, never compiled into a collector. Repair means a collector that cannot reach its source says so. The carve-outs are made concrete rather than generic. Anything that deletes is never brief and never autonomous. Detecting ABSENCE is first-class, because our own dgx1 incident had the kernel OOM-kill promtail and the signature was nvidia-smi returning N/A and a scrape gap -- a rule that fires above 90% never fires when the exporter is already dead. And a dispatcher that silently drops an alert manufactures the appearance of health. Carries one monitoring-specific corollary to the leave-a-check rule: a rule that cannot fire is worse than no rule, because it is a promise of coverage that will be believed. Names the one real tension between the two sources -- UNIX says build afresh rather than complicate old programs, ponytail says reuse what is in the codebase -- and resolves it: reuse a component, never widen something that does a different job until it covers yours. --- AGENTS.md | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 63 +--------------- 2 files changed, 213 insertions(+), 62 deletions(-) create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b1e2225 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,212 @@ +# AGENTS.md — monitor-agent + +This file guides coding agents working in this repository. `CLAUDE.md` is a +symlink to it, matching the convention in `herdr/` and the workspace root. + +**Read §"Design doctrine" before proposing anything.** It is not preamble; it +is the standard a change is judged against here. + +## Project Purpose + +monitor-agent is a standalone Rust daemon + ratatui TUI for monitoring systems +and alerting a human operator of coming problems — before they become crises. + +It is NOT a replacement for gila-monitor-tui (monty-tui), which owns the +gilabot swarm/agent ecosystem view. monitor-agent is a general-purpose, +ecosystem-independent operator alert tool. + +## Workspace Structure + +``` +monitor-agent/ +├── monitor-core/ Types, traits, config, alert engine +├── monitor-collect/ Collectors: local, prometheus, nats, ssh +├── monitor-alert/ Dispatchers: terminal bell, voice, webhook, nats-pub +├── monitor-journal/ Append-only, hash-linked journal of metric snapshots +├── monitor-presence/ Frontend-agnostic presence model (state + intents) +├── monitor-station/ Caster station (Phase 0/1 scaffold) +├── monitor-tui/ ratatui dashboard +├── monitor-gui/ GUI skin over the same presence model +├── monitor-voice/ Speech dispatch backends +└── monitor-cli/ Binary entry point (monitor-agent) +``` + +Each name is a sentence about one responsibility — see the doctrine's closing +section. Keep it that way. + +## Build Commands + +```bash +just check # fmt + clippy + test (full local gate) +just test # cargo test --workspace +just install # release binary to ~/bin +just install-hooks # wire .githooks/pre-push +cargo run --bin monitor-agent -- tui # launch TUI +cargo run --bin monitor-agent -- doctor # check collectors +``` + +## Key Design Rules + +- **Zero-warnings policy**: `cargo clippy -- -D warnings` must be clean before any merge. +- **Faux PR workflow**: branch → TDD → all tests pass → merge to main. +- **No push without hooks**: `just install-hooks` after any fresh clone. +- **Config search path**: `MONITOR_CONFIG` env → `./monitor-agent.toml` + → `~/.config/monitor-agent/config.toml` → `/etc/monitor-agent/config.toml` +- **Collector trait**: add new data sources by implementing `monitor_core::metrics::Collector`. +- **Dispatcher trait**: add new notification channels by implementing + `monitor_core::alert::AlertDispatcher`. + +## Roadmap + +See `docs/ROADMAP.md`. Next phase: IPC socket daemon/TUI split (Phase 11). + +## Logos + +Source image: `docs/logos/Monty_Lizard_Large.png`. +Regenerate ANSI/ASCII art via `chafa` — see `docs/logos/README.md`. +The TUI splash selects width automatically via `monitor_tui::splash_for_width()`. + +## Dependencies of Note + +| Crate | Why | +|---|---| +| `sysinfo 0.33` | Local CPU/mem/disk/process metrics | +| `ratatui 0.29` + `crossterm 0.28` | TUI framework (match monty-tui versions) | +| `async-nats 0.37` | NATS subscriber + publisher | +| `reqwest 0.12` (rustls-native-roots) | Prometheus HTTP, webhook — respects OS CA store | + +--- + +# Design doctrine + +Two sources, and they are not decoration. Every design question in this repo is +answered by asking them in order. + +- **[ponytail](https://github.com/DietrichGebert/ponytail)** — *how much code + should exist.* "The best code is the code you never wrote." +- **[The UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy)** — + *what shape it takes.* "Make each program do one thing well. Expect the + output of every program to become the input to another." + +They agree far more than they differ, and where they differ it is worth knowing +why (see "The one real tension"). + +## The ladder — run it before writing code, not instead of reading it + +Stop at the first rung that holds: + +1. **Does this need to exist at all?** Speculative need → skip it, say so in one + line. +2. **Already in this repo?** A trait, type, collector, or dispatcher that exists + → reuse it. Re-implementing what lives one crate over is the most common + waste. +3. **Stdlib?** Use it. +4. **Native platform feature?** A systemd timer over a scheduler thread; the + kernel's own accounting over a sampler. +5. **A dependency already in `Cargo.toml`?** Use it. Never add one for what a + few lines can do. +6. **One line?** One line. +7. **Only then**, the minimum that works. + +**The ladder shortens the solution, never the reading.** Trace the real flow +first — every crate the change touches — then climb. The smallest diff in the +wrong place is not laziness, it is a second bug. + +Worked example, from this repo's own design work: *"the monitor should clean up +full disks."* Rung 2 stops it. `~/bin/cleanup-stale-targets.sh` and +`weekly-disk-cleanup.sh` already encode the "is this safe to delete" judgement +and have been exercised. The correct change is **invoke the existing reaper**, +not grow a second `rm` implementation. One implementation of a destructive +decision, never two. + +## The UNIX rules that bind hardest *here* + +All of them apply. Four decide most arguments in a monitoring daemon. + +**Rule of Silence — when there is nothing surprising to say, say nothing.** +This is the single most important rule for this project. A monitor that chatters +trains its operator to ignore it, and an ignored monitor is worse than none +because it also consumes the belief that something is watching. Every alert must +earn its interruption. Default to quiet; make noise the exception you can +justify. + +**Rule of Composition — output is another program's input.** Alerts and metric +snapshots are structured events (NATS/JSON) that another tool can consume, not +prose formatted for a human eye. The TUI is *a* consumer of that stream, never +the only way to get at the data. If a fact is reachable only by looking at a +dashboard, it is not composable and does not count as reported. + +**Rule of Separation — mechanism apart from policy.** Collectors gather; +dispatchers deliver; **config decides what is worth alerting on**. A threshold +compiled into a collector is a bug. This is already the shape (`Collector` and +`AlertDispatcher` traits + TOML); keep it. + +**Rule of Repair — repair what you can, but when you must fail, fail noisily and +as soon as possible.** A collector that cannot reach its source must say so +loudly. Which leads directly to the next section. + +## Where laziness is forbidden + +ponytail carves these out and they are absolute. In this repo they are: + +**Anything that deletes.** Remediation is not a place to be clever or brief. +Only ever act on what is provably reconstructible (a build cache rebuilds; a +worktree with uncommitted changes does not). Destructive acts — evicting a +model, removing dirty state, rebooting a host — are **never autonomous**; they +are proposed and wait. + +**Detecting absence.** A rule of the form *"alert when disk > 90%"* never fires +if the exporter is dead. Our own incident is the proof: the kernel OOM-killed +`promtail`, and the signature was `nvidia-smi` returning `N/A` and a scrape gap, +not a threshold crossing. **Staleness of a series is a first-class alert +condition, not a nice-to-have.** A check that reports nothing must never be +indistinguishable from a system that is fine. + +**Alert delivery.** A dispatcher that silently drops an alert is the worst +possible failure — it manufactures the appearance of health. Delivery failure is +itself an alert-worthy event. + +Input validation at trust boundaries, error handling that prevents data loss, +and security posture are never simplified away. + +## Every non-trivial change leaves one runnable check + +A branch, a parser, a threshold comparison, a dispatch path: leave the smallest +thing that fails if the logic breaks. No frameworks, no fixtures, no per-function +suites unless asked. Trivial one-liners need none — YAGNI applies to tests too. + +**Monitoring-specific corollary: a rule that cannot fire is worse than no rule.** +When adding an alert rule, prove it fires — feed it the condition and watch it +trigger. An untested rule is a promise of coverage that does not exist, and it +will be believed. + +## `ponytail:` markers + +A deliberate simplification with a known ceiling gets a comment naming the +ceiling and the upgrade path: + +```rust +// ponytail: linear scan over rules; index by metric name if rule count grows +``` + +Harvest them before planning work. A shortcut that is written down is a +decision; one that is not is a trap. + +## The one real tension, and how it resolves + +UNIX says *"build afresh rather than complicate old programs by adding new +features."* ponytail's rung 2 says *"already in this codebase? reuse it."* These +can pull apart. + +**Resolution: reuse a component; never bolt a feature onto something that does a +different job.** Adding a tenth responsibility to `monitor-core` because it is +already there is the failure both philosophies warn about — it violates "one +thing well" *and* produces more code than a new crate would. Reuse means calling +the thing that already does the job. It does not mean widening a thing that +does a different job until it covers yours. + +The ten crates here are the evidence this is already understood: +`monitor-collect` gathers, `monitor-alert` dispatches, `monitor-journal` keeps +an append-only hash-linked record. Each name is a sentence about one +responsibility. **A new crate whose purpose cannot be stated in one clause is +the wrong shape.** diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 3b1b862..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,62 +0,0 @@ -# CLAUDE.md — monitor-agent - -## Project Purpose - -monitor-agent is a standalone Rust daemon + ratatui TUI for monitoring systems -and alerting a human operator of coming problems — before they become crises. - -It is NOT a replacement for gila-monitor-tui (monty-tui), which owns the -gilabot swarm/agent ecosystem view. monitor-agent is a general-purpose, -ecosystem-independent operator alert tool. - -## Workspace Structure - -``` -monitor-agent/ -├── monitor-core/ Types, traits, config, alert engine -├── monitor-collect/ Collectors: local, prometheus, nats, ssh -├── monitor-alert/ Dispatchers: terminal bell, voice, webhook, nats-pub -├── monitor-tui/ ratatui dashboard -└── monitor-cli/ Binary entry point (monitor-agent) -``` - -## Build Commands - -```bash -just check # fmt + clippy + test (full local gate) -just test # cargo test --workspace -just install # release binary to ~/bin -just install-hooks # wire .githooks/pre-push -cargo run --bin monitor-agent -- tui # launch TUI -cargo run --bin monitor-agent -- doctor # check collectors -``` - -## Key Design Rules - -- **Zero-warnings policy**: `cargo clippy -- -D warnings` must be clean before any merge. -- **Faux PR workflow**: branch → TDD → all tests pass → merge to main. -- **No push without hooks**: `just install-hooks` after any fresh clone. -- **Config search path**: `MONITOR_CONFIG` env → `./monitor-agent.toml` - → `~/.config/monitor-agent/config.toml` → `/etc/monitor-agent/config.toml` -- **Collector trait**: add new data sources by implementing `monitor_core::metrics::Collector`. -- **Dispatcher trait**: add new notification channels by implementing - `monitor_core::alert::AlertDispatcher`. - -## Roadmap - -See `docs/ROADMAP.md`. Next phase: IPC socket daemon/TUI split (Phase 11). - -## Logos - -Source image: `docs/logos/Monty_Lizard_Large.png`. -Regenerate ANSI/ASCII art via `chafa` — see `docs/logos/README.md`. -The TUI splash selects width automatically via `monitor_tui::splash_for_width()`. - -## Dependencies of Note - -| Crate | Why | -|---|---| -| `sysinfo 0.33` | Local CPU/mem/disk/process metrics | -| `ratatui 0.29` + `crossterm 0.28` | TUI framework (match monty-tui versions) | -| `async-nats 0.37` | NATS subscriber + publisher | -| `reqwest 0.12` (rustls-native-roots) | Prometheus HTTP, webhook — respects OS CA store | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file