AGENTS.md · git:20261015.a96fa4d · 2026-10-15 · sha256 0c30524fad9e2ed9

AGENTS.md git:20261015.a96fa4dA

Immutable. This exact content never changes and is served at /api/v1/blob/0c30524fad9e2ed9.

# Niles

Rust CLI that coordinates coding agents from different model families in tmux. Single crate,
Unix only. Discover modules and commands from the source and `niles --help` rather than a list
here.

Machine-specific or private notes go in `AGENTS.local.md` (gitignored); read it if present.

## Git

- Branch for new work, named `<issue>-<slug>`. Don't commit to `main` unless told to.
- Commit and push only when explicitly asked. Finishing a change is not a cue to commit it.

## Checks

[CI](.github/workflows/ci.yml) runs the checks on push; don't rerun them locally. Before
reporting a change done, run only what CI doesn't cover, such as the focused tests for the
code you changed, and say what printed.

## Standards

- Simple, idiomatic, first-principles. Find root causes; no shortcuts, temporary fixes, or
  "just in case" code.
- Minimal diffs: touch only what the change needs.
- APIs, internal ones included, are self-documenting and hard to misuse.
- Names and types carry meaning. Comment only what the code can't say: why, invariants,
  non-obvious tradeoffs. No comments that restate the code.
- For a non-trivial change, ask whether there is a more elegant way before presenting it. Don't
  over-engineer simple fixes.

## Fail loudly

- No silent defaults on failure: propagate with `?` or fail fast.
- A default is a decision. Make it at the parse boundary or as a named const, never inline at a
  use site. A site that truly owns the decision takes
  `#[expect(clippy::disallowed_methods, reason = "…")]`.
- No catch-and-continue; invalid state stops.
- No branches for impossible states; encode impossibility in types.

## Modularity

- Source files stay under ~500 lines; split by responsibility, one module per job.
- DRY by the rule of three; don't abstract at two occurrences.
- Never copy-paste-tweak; extract a function or generic.
- No pass-through functions unless they change visibility, satisfy a trait, or cross a module
  boundary under a name the caller's domain needs.

## Correctness

- Make invalid states unrepresentable with enums and newtypes. A newtype must carry weight: an
  invariant enforced at construction, a distinction between same-typed values, or impls the inner
  type can't have.
- Parse, don't validate: type raw input (manifests, status lines, tmux output) once at the
  boundary.
- `debug_assert!` invariants the types can't express.

## Lints

Mechanically checkable rules live in `[lints.clippy]` in `Cargo.toml` and in `clippy.toml`, not in
prose. The gate lints all targets. Suppress a lint with `#[expect(…, reason = "…")]`, not
`#[allow]`. The one exception is test code, which allows `unwrap_used` and `expect_used` at the
crate root; `tests/common` may use `#[allow(dead_code)]`, because each test binary uses a different
subset of it.

## Invariants

- Role prompts live in `prompts/` and are compiled in with `include_str!`. They are the
  product's behavior, so edit them with the same care as code.
- Agent families and their launch mechanics are defined in `src/agents/families.rs`; models and
  effort levels in `src/agents/roster.yaml`.
- `niles wait` reserves stdout for wake lines; diagnostics go to stderr. Its exit codes (0, 10, 11,
  12, 22) are a contract the lead relies on.
- Changes that move a trust boundary (agent execution, workspace state, pane text) update the
  [threat model](docs/security.md).

To check new agent CLI releases or what Niles should change for them, follow
[CONTRIBUTING.md#agent-cli-compatibility](CONTRIBUTING.md#agent-cli-compatibility).