Imported from janosh/dotfiles (
agents/AGENTS.md). Install upstream withnpx skills add janosh/dotfiles --skill agents. Copyright stays with the author.
Global Agent Instructions
These rules apply to all projects.
Correctness (don't bullshit)
- Equivalence is measured, not read. To claim two implementations match, run both on identical inputs (representative and edge cases) and paste
max |a-b|and max relative error. Never pronounce on equivalence from reading the code. - Round-off has a size, compute it. Never wave off a numerical mismatch as "floating-point", "round-off", "precision", or "tolerance" without computing the actual error and comparing to machine eps for the dtype (f64 ≈ 2.2e-16, f32 ≈ 1.2e-7). Bisect it: compare intermediate values, not just final outputs, and locate the first step where the paths diverge.
- Compare with explicit, justified tolerances. Use
np.testing.assert_allclose(rtol=..., atol=...)with values you chose deliberately (not defaults). For "bit-identical" claims use exact equality. Pin seeds before comparing stochastic outputs. - Verify before claiming. Read relevant implementations and callees, distinguish observed results from inference, and never report unrun checks as passing.
Multi-agent branch sharing
Multiple agents may work on the same branch concurrently. Editing a file that already has uncommitted changes from another agent is fine, don't be timid, except at git time: when staging and committing. Don't stage changes unless asked to commit. Include only the changes relevant to your task and use explicit git add <your-files> instead of git add -A so you don't commit, revert, or stash another agent's out-of-scope work. Only exception being if other agent's work looks related or too menial to warrant it's own commit, then just include in your commit.
Python Projects (*.py, pyproject.toml)
- Always add typing annotations to functions and classes, including return types
- Add descriptive docstrings to all functions and classes
- Don't add shebangs (no
#!/usr/bin/env python) - Run scripts with
uv run script.py, not barepython script.py.uvpicks the env itself: a project's existing.venv/, an isolated env for scripts declaring PEP 723 inlinedependencies, the active~/.venv/py314otherwise. Never create venvs unprompted; don't want per-projectvenvs in repos that lack one. - Use
pytestfor testing, neverunittest - All tests go in
./testswith concise single-line docstrings - Use
pytest.mark.parametrize()to cover multiple parameter values - Use
np.random.default_rng(seed=0)not the legacynp.random.*API - Prefer
os.path.isfile/isdiroveros.path.exists - Use f-strings for paths, not
os.path.join() - Prefer
os.pathoverpathlib.Path(except withtmp_pathfixture) - Use
tyfor type checking, nevermypy,pyright, or others - NEVER use
__all__! We discourage star imports—they break static analysis of types and imports - Use
time.perf_counter()instead oftime.time()for wall-time measurements - Always prefer
plotlyovermatplotlibfor plotting. When exporting to HTML, always useinclude_plotlyjs="cdn"for much smaller file sizes (3 KB vs 3 MB). - Never use
fig.add_trace(go.Scatter(...))— usefig.add_scatter(...),fig.add_bar(...),fig.add_histogram(...), etc. directly. Shorter, avoids the redundantgo.import for trace types, and lets plotly validate args at call time. - **In
notebooks/, prefer pymatviz widgets instead:BarPlotWidget,HeatmapMatrixWidget,HistogramWidget,ScatterPlotWidget,StructureWidget,ConvexHullWidget,TrajectoryWidget,PhaseDiagramWidget, etc. overplotlyormatplotlibfigures. Check existing demos/notebooks for usage and API patterns before writing new visualization code. - avoid
typing.castunless absolutely necessary
TypeScript/Svelte Projects (.ts,.svelte)
- Use snake_case for variables and functions, not camelCase
- Prefer backticks to single or double quotes, except inside TypeScript type definitions
- Use
it.each([...])andtest.each([...])for parameterized vitest tests - Never use
/**or/* */for docs (JSDoc/TSDoc included). Always//, with consecutive//lines for multi-line notes. Reserve/* */for CSS and tool pragmas (eslint-disable, etc.) - In CSS/style blocks, don't leave blank lines between rules - the closing
}is enough separation - Keep CSS simple: prefer nested selectors over many classes; inline styles if a class only has 1-2 rules. offer to remove CSS classes that aren't used at all.
- Prefer attachments over the legacy
use:directive for actions - Avoid
switchstatements, prefer simpleif/elsechains - Prefer arrow functions for direct-return functions (body is a single
return), e.g.const f = (x) => x + 1overfunction f(x) { return x + 1 } $derivedis writable! Don't use$state+$effectwhen$derivedwith later reassignment works- Pass Svelte
$statevariables (not plain values) tobind:-able props to avoidstate_referenced_locallywarnings. e.g. avoidx_axis={{ label: 'foo' }}ifx_axisis bindable. instead definelet x_axis = $state(label: 'foo')and passbind:x_axisto component. - Never use
anytype! Useunknownand narrow, or define proper types - Avoid
!non-null assertions—narrow types instead - Destructure props:
const { name, age } = useroveruser.name, user.agerepeatedly - Don't split components on line count alone—core ones run past 2000 lines by design. Only offer an extraction when a file has clearly separable responsibilities.
- Prefer
format_numfrommattervizover.toFixed()for number formatting (handles SI prefixes, trailing zeros) - Use
SvelteSet/SvelteMaponly where the collection is UI state that templates or effects must react to; plainSet/Mapfor constant lookup tables, function-local scratch and non-reactive bookkeeping (timers, caches)
Git & GitHub CLI
- Git editor is
code --wait, so interactive commands hang. Override it with asedscript instead, e.g.GIT_EDITOR="sed -i '' 's/pick/squash/2'" git rebase -i HEAD~n. - If
ghcommands fail with auth errors (e.g. "Unauthorized"), try switching accounts:gh auth switch(don't ask permission for this, just do) - Don't commit without being asked
- Never add
Co-authored-by: Cursor/Codex/...or similar to commit messages - Never
git push --force/--force-with-leaseunless explicitly asked for that specific branch and situation. Once work is pushed, prefer follow-up commits over amending/rebasing. - A request to submit or open a PR authorizes publishing its branch and creating the PR without another confirmation. Create issues only when requested. When asked to "draft" an issue or PR, output the title and body as markdown for review — don't run
ghuntil explicitly told to post. - Permission to commit is never permission to open a PR, and neither is a blocked push. If a push is rejected (branch protection, required status checks, ruleset), stop and report it. Do NOT work around it by moving the commit to a new branch and opening a PR. Leave the commit local and let the user choose how to land it.
- Do not ask for separate permission to publish a branch as part of requested work, including setting an upstream for a new branch. Verify the destination with
git push --dry-run. For an existing fork PR, push to its actual head repository and branch rather than creating an unrelated branch onorigin. - Asking a question mid-task and getting an answer authorizes only what was asked. When the plan turns out not to work, ask again. Don't substitute a different action you think is equivalent.
CRITICAL: Protect Uncommitted Work
NEVER run git reset, git checkout <file>, git stash, or git clean on modified/untracked files without explicit user approval!
Multiple agents work on the same repo concurrently. Any destructive git operation (reset, checkout -- <path>, stash, clean, restore) can silently destroy another agent's in-progress work. This includes files you didn't modify — they may belong to a parallel agent. Always ask before discarding anything.
- If you need a clean working tree for your task, use
git worktree addinstead of stashing - Check with
ls -laandfile <path>before deleting—directories may be symlinks to working copies
General
- Before any cluster or
matscidatabase work, read~/dev/dotfiles/tmp/cluster-db-access.md(SSH aliases, partitions, containers, build setup, DB recipes) - Units notation: never use
ųor other obscure Unicode glyphs for units. WriteA^3(cubic angstrom),e/A^3(electron density),eV/A(force), etc. In Rust doc comments and Python docstrings useų,e/ų,eV/Åwith the standard Å character. - No single-letter or concatenated variable names! Use proper snake_case:
idxnoti,n_imagesnotnimages,f_maxnotfmax,col_idxnotcolidx - No fallbacks or backward-compatible interfaces unless explicitly told. Throw an error or fail early—silent catches, default shims, and compatibility wrappers mask bugs.
- Remove dead code aggressively. Prefer a clean codebase over deprecation.
- Log useful context with errors—include relevant variable values
- Ask before adding new dependencies.
- For my own upstream packages (e.g.
svelte-widgetsandmatterviz), always track the upstream repository's latestmainbranch. Adapt consuming code to breaking API changes instead of pinning older commits or releases, downgrading, or adding compatibility fallbacks. - Prefer editing existing files over creating new ones.
- Prefer fast Rust CLIs over stock Unix tools when available:
fdoverfind,dustoverdu,rgovergrep, andbatovercat. - Keep existing comments when editing files unless they are stale or low value.
- Use single-line section headers:
// === Section Name ===not verbose multi-line box comments - Never hard-wrap markdown. There is no max line length: write each paragraph and list item as one long line and let the renderer wrap it. Reflowing prose to a column makes later edits produce noisy diffs. The only exception is inside code fences, where line breaks are content. This applies to prose you write for humans too, e.g. Slack messages, PR descriptions, commit bodies.
- Never add an
Unreleasedsection to a changelog unless the user explicitly asks for one. - Never create or commit package-manager artifacts: lock files (
uv.lock,pnpm-lock.yaml,package-lock.json,deno.lock, etc.) or metadata such aspnpm-workspace.yaml. Preferuv run --no-project(or keepUV_FROZEN=1/UV_NO_SYNC=1); do not runuv lock/uv syncin repos that omit a lockfile. - Never commit handover docs, temp data files, or proof-of-concept artifacts (no
HANDOVER.md, sample.jsonl/.lmdbfiles, exploratory notebooks, etc.). These clutter the monorepo — keep them local or intmp/. - Use
prek(Rust port), neverpre-commit(Python) - Never take an unrequested irreversible or externally-visible action — opening PRs/issues, pushing branches, deleting remote refs, posting to Slack, commenting on GitHub. When a task stalls, report and ask; don't improvise a workaround that creates something the user has to undo.
- Run commands yourself to collect logs/errors—don't ask the user. Run tests (
pytest,vitest,playwright) or scripts, start dev servers, visit pages in browser, take actions to reproduce issues. - Avoid redundant verification. Do not run tests solely to establish a green baseline. Batch edits and run the narrowest affected checks once after the batch. Reuse a same-session result only when it covered the path and nothing edited that path since. Escalate to broader suites, lint, types, coverage, or mutation checks only when the change's scope or risk warrants them. Workflows whose purpose is repeated failure testing, such as mutation checks, are exempt.
- When you begin risky edits in a tree that is already dirty, capture its pre-edit diff first; you cannot reconstruct it later. If an unexpected failure cannot be attributed from the diff and control flow, diagnose it in a temporary worktree — never by stashing, resetting, restoring, or checking out files in the active tree. Compare the failing check on the clean base and on that base plus only your patch; where you captured a pre-edit diff, compare base plus that pre-existing patch against base plus both patches. Account for untracked inputs and missing build environments before attributing the failure.
- In large codebases, run the narrowest relevant tests, never the full suite by default. Run thousands-test suites only when explicitly requested or when broad, cross-cutting changes cannot be validated otherwise; unrelated tests waste time and machine resources.
- When fixing a bug or making a behavior tweak, ALWAYS add or update a unit test that would have caught it. Prefer extending an existing related test over creating a new test to avoid extra setup/teardown bloat. Only create a new test when there is no related test to extend.
Response shape
Optimize replies for scanning. The reader should get the answer from the first line and know what's next from the last.
- Lead with the answer. When it's a command, path, or
file:line, put that literal thing in the first line and explain after. - Number multi-step instructions, one bounded action per step. Fold trivial steps into their neighbor.
- Rank list items by what matters most. When a list runs long, split it into "do now" vs "later" rather than leaving it flat.
- Finish one thing before raising the next. Park new issues as concise questions at the end, never mid-answer.
- Report errors matter-of-factly: location, cause, fix. Never "oh no"/"there seems to be a problem"/...
- When work is done, say what now works and give the command to see it: "Login works with magic links. Try
npm run dev, open/login."