AGENTS.md — orientation for AI coding agents on vibe-qc

This file is for any AI coding assistant working on this repo (aider + DeepSeek / Llama / GPT, Claude Code, Cursor, …). It is the single entry point: read this first, then follow the pointers below. If you are a Claude Code session, also read CLAUDE.md in full — it carries the same project rules with Claude-specific detail.

Keep this file accurate when the project shifts; it is committed and shared across every contributor and agent.


What vibe-qc is

vibe-qc is a quantum-chemistry code for molecules and solids — a Python frontend over a C++17 core. It computes electronic-structure energies, gradients, and properties:

  • Molecular — RHF / UHF, RKS / UKS DFT (all of libxc’s 500+ functionals), RMP2 / UMP2, analytic nuclear gradients, D3(BJ) dispersion, post-SCF properties. Validated against PySCF to machine precision.

  • Periodic — 1D / 2D / 3D Hartree–Fock and KS-DFT, Monkhorst–Pack k-meshes, Ewald summation, band structure + DOS. Stable in the molecular-limit regime; being hardened for tight-cell bulk work.

  • Long-term target — the cyclic cluster model (CCM) for solids (the v2.0 feature). See docs/roadmap.md.

The native numerical core links libint (Gaussian integrals), libxc (XC functionals), spglib (crystal symmetry), libecpint (ECP integrals), FFTW3 (FFT), Eigen, and pybind11 — all vendored under third_party/ by scripts/setup_native_deps.sh. Licensed MPL 2.0.

Naming: the brand is vibe-qc (hyphen), the Python package is import vibeqc (no hyphen). Same code, two spellings. Not on PyPI yet — install is `git clone + ./scripts/setup_native_deps.sh

  • pip install -e .perdocs/installation.md`.


Repo map — where things live

python/vibeqc/        Python API. Molecular + periodic drivers, ASE
                      integration, I/O, banner, SCF-log formatting.
  runner.py             molecular run_job / run_rhf / run_rks / … entry
  molecule.py           Atom, Molecule
  periodic_runner.py    periodic SCF entry
  periodic_*_dispatch.py  RHF / RKS driver dispatch (Ewald vs GDF paths)
  periodic_*.py         periodic Fock / density / gradient / GDF blocks
  kpoints.py            Monkhorst–Pack meshes, IBZ reduction, band paths
  ase.py / ase_periodic.py   ASE Calculator integration
  banner.py             runtime version banner (vibe-qc + linked libs)
  scf_log.py / structured_log.py   .out / NDJSON output formatting
  basis_crystal.py      CRYSTAL-format basis-set parser
  basis_library/        bundled basis sets (.g94); ecp_library/ for ECPs
  output/               unified output infrastructure
    plan.py               OutputPlan, OutputRole, OutputFormat, PlannedFile
    writer.py             OutputWriter — per-job coordinator, owns manifest
    manifest.py           ManifestUpdater, FileOutcome — .system TOML lifecycle
    formats/              per-format writers (xyz, molden, cube, cif, …)
      qvf.py                QVF (.qvf) writer + validator + CLI
      qvf_manifest.schema.json  JSON Schema for manifest.json

cpp/include/vibeqc/   C++17 public headers (integrals, SCF, DFT grid, …)
cpp/src/              C++17 implementation
  bindings.cpp          pybind11 bindings — THE Python <-> C++ boundary
  rhf.cpp uhf.cpp rks.cpp uks.cpp   molecular SCF kernels
  integrals.cpp ecp.cpp df.cpp      integral kernels
  periodic_*.cpp        periodic Fock / SCF / gradient / XC
  ewald.cpp fft_poisson.cpp lattice_sum.cpp   periodic Coulomb
  diis.cpp guess.cpp linear_dependence.cpp    SCF convergence machinery

tests/                pytest regression suite — every commit must pass
docs/                 Sphinx + Furo docs site (deployed at vibe-qc.com)
handovers/            long-lived per-workstream handover trackers (see rule 10);
                      handovers/README.md is the index
examples/             runnable example scripts (molecular/ periodic/ …)
studies/              research studies (e.g. asbestos-polymorphs)
scripts/              setup_native_deps.sh + per-dep build helpers
third_party/          vendored native deps (gitignored, build artifact)
vibe-queue/           co-located `vq` job-queue tool — SEPARATE sub-project
                      with its own pyproject.toml; don't entangle it with
                      the QC core unless the task is explicitly about vq.

When you need to find something:

Looking for…

Start at

How to produce a visualisation archive

python/vibeqc/output/formats/qvf.py, docs/handover_qvf_writer.md, docs/design_qvf_format.md

How to work with basis sets

python/vibeqc/basis_toolkit/, docs/design_qvf_basis.md, docs/qvf_basis_developer_readme.md

How to invoke a calculation

python/vibeqc/runner.py, docs/quickstart.md, docs/tour.md

Molecular SCF math

cpp/src/rhf.cpp / uhf.cpp / rks.cpp / uks.cpp

Periodic SCF math

cpp/src/periodic_*.cpp, python/vibeqc/periodic_*.py

The Python↔C++ API surface

cpp/src/bindings.cpp

A concept explained for users

docs/user_guide/<concept>.md

What’s known-broken right now

docs/index.md (the maintenance-window admonition) + CHANGELOG.md

What’s planned

docs/roadmap.md

Per-component licensing

docs/license.md


Build, test, docs

# one-time: fetch + build vendored native deps (15-40 min, idempotent)
./scripts/setup_native_deps.sh

# editable install into a project-local venv
python3 -m venv .venv
.venv/bin/pip install -e '.[test]'

# verify
.venv/bin/python -c "from vibeqc import print_banner; print_banner()"
.venv/bin/python -m pytest tests/        # must pass before any commit

# docs
sphinx-build -b html docs/ docs/_build/html

Requires Python 3.11+ and a C++17 compiler. Full per-platform dependency lists are in docs/installation.md.


Code style

  • C++17. Four-space indent, brace on the same line for control flow. Everything in cpp/ lives under namespace vibeqc { }. Match cpp/src/rhf.cpp / cpp/src/integrals.cpp.

  • Python 3.11+. PEP 8, four-space indent. Start every module with from __future__ import annotations. Type hints on public API; local helpers may skip them.

  • Prefer editing existing modules over adding new ones. If a new file is genuinely the right call, follow the neighbours’ layout.

  • Don’t add dependencies casually — new native deps need a roadmap conversation; new Python deps belong in optional extras.

Full contributor note: CONTRIBUTING.md.


Project rules an agent MUST follow

These are non-negotiable. The authoritative, fully-explained version is CLAUDE.md — read it. Condensed:

  1. Licensing discipline. vibe-qc is MPL 2.0 and ships to academic

    • open-source users. Before bundling/vendoring/fetching any new data or dependency, check redistribution terms. When unclear, don’t bundle — use the on-demand fetcher pattern. Escalate licensing questions to the maintainer.

  2. No imports from other QC programs. vibe-qc has its own implementation of every method it ships. Never add import pyscf (or psi4, orca, cp2k, vasp, nwchem, gamess, …) to anything under python/vibeqc/ or cpp/. Libraries (libint, libxc, spglib, libecpint, Eigen, FFTW3, pybind11, ASE, NumPy, SciPy) are linkable building blocks and fine. The litmus test: “does this run on its own as a QC program?” If yes, it’s forbidden as a runtime import. Parity testing against external programs is done out-of-process via subprocess runners (examples/regression/runner_<program>.py).

  3. Branch model + push discipline. main is active development; release is fast-forward-only public. To push: git fetch, git pull --rebase origin main, then git push origin HEAD:main. Never force-push main/release, never git commit --amend a pushed commit, never skip pre-commit hooks (--no-verify) unless the maintainer explicitly asks.

  4. Every commit ships green. Pre-commit hooks pass, tests pass, CI passes. If a hook fails, fix the cause — don’t bypass it. Activate the personal-info hook once per clone: git config --local core.hooksPath .githooks. CI status is queryable from the CLI: glab api "projects/19/pipelines?per_page=5" (numeric project id; the glab ci subcommands 404 against this instance). Full recipe: CLAUDE.md § 2 “Checking CI after a push”.

  5. Periodic SCF oscillation = a bug, not a convergence problem. If a periodic SCF oscillates or lands at an impossible energy, don’t paper over it with damping / level-shift / quadratic fallback / threshold-tuning. Reproduce against a reference, find the gauge / Madelung / image-summing root cause, file a regression test.

  6. Privacy hygiene. The repo is public. Never commit credentials, real IP addresses, or author home paths (/Users/<name>/…, /home/<name>/…) — use ~/, <vibe-qc-checkout>, or a placeholder. The maintainer identity in repo content is mpei@vibe-qc.com and nothing else.

  7. Scope discipline — escalate, don’t decide unilaterally. No grand refactors without maintainer approval. Stop and ask the maintainer on: licensing questions, branch-policy edge cases, CHANGELOG/release-note wording, anything contradicting a rule above, or anything you’d describe as “pretty sure this is fine but…”. Asking is cheap; wrong-and-pushed is expensive.

  8. Citation database ownership. Any merge that adds, removes, or renames a method, functional, basis set, ECP, dispersion model, or third-party linked library to vibe-qc MUST update the citation database at python/vibeqc/output/citations/database.toml in the same merge:

    • add or remove the matching [entries.<key>] block,

    • add, update, or remove the matching route under [routes.<category>] (software / integrals / basis_sets / functionals / methods / libraries),

    • extend tests/test_citations.py so the feature triggers the expected references (the _REQUIRED_FUNCTIONALS / _REQUIRED_BASIS_SETS parametrised lists are the belt-and-braces coverage gate — add yours).

    The database is the single source of truth: docs/citing.md and docs/user_guide/functionals.md § Citations will be auto-rendered from it (Sphinx directive, Phase O7). Do not hand-edit those sections to introduce new references — update the database, and the docs follow.

    On the basissetdev branch the 87 BSE-fetched basis sets get a sibling database_basissetdev.toml; that file does not exist on main (per rule 4 of CLAUDE.md, basissetdev does not merge into main for v0.8.x).

    Full schema + the runtime assembly contract live in docs/design_output_module.md. Skipping this rule means published runs will silently omit a reference and the maintainer has to chase the gap after the fact — please don’t.

    Companion PDF library (optional). If a ../library sibling repo is present — the maintainer’s reference-PDF library, the physical counterpart of database.toml — and you add a citation whose paper isn’t already there, append its DOI to ../library/download-doi.txt: a comment line plus the bare DOI under the matching # === <topic> === section (format per that repo’s README.md). That file is your only write target — never commit, fetch PDFs, run its sync scripts, or modify anything else in that repo; the maintainer acquires PDFs separately. Skip silently if ../library isn’t present.

  9. Stay wired into main — land small, ship release-ready. Land work in small milestones that each leave main tag-able, not merely compiling. The 0.8.x / 0.9 releases hurt because chats drifted and landed half-finished work the release chat then had to audit. Before every push, verify repo-wide: full build + full test suite green, CI green on main, no half-finished code paths, incomplete features self-gated (flag / experimental marker / basissetdev-conditional) with the decision documented, CHANGELOG [Unreleased] accurate (claim only what is done and tested), and docs in parity this milestone. After every git pull --rebase, review what other chats landed (git log <last-sync>..origin/main) and re-test if it touches your area — a clean rebase catches textual conflicts only, not a changed API or behaviour. Keep a persistent handover file current (the .release-status/ drop-box, or handovers/HANDOVER_<TOPIC>.md) so another session can resume cold. Full detail: CLAUDE.md § 14.

  10. Handovers live in handovers/ — keep them there, retire when done. Long-lived per-workstream trackers live in the top-level handovers/ directory, one file per workstream named HANDOVER_<TOPIC>.md, indexed by handovers/README.md. Rules:

    • Do not create handovers at the repo root, and do not put them under docs/. They are dev coordination artifacts, not user docs, and the .githooks/check_no_em_dashes.py pre-commit hook bans em/en dashes in the prose of any docs/**/*.md (it scans the full staged content, so even moving or path-editing a dash-bearing file under docs/ trips it). handovers/ is deliberately outside that scope. New handover → handovers/HANDOVER_<TOPIC>.md; add a line to handovers/README.md.

    • Retirement = deletion (not archival) once the workstream is complete and landed: the record stays in git history (git log --all -- handovers/HANDOVER_<TOPIC>.md). Before deleting, grep repo-wide for inbound references (code / tests / docs / CLAUDE.md / CHANGELOG [Unreleased]); a handover that code still cites by name is kept until those anchors are repointed. Precedent: 535ed7f0, 58d84f4c.

    • Reference handovers by their handovers/… path from code comments and docs. Never point a user-facing error/warning string at a handover — send users to docs/user_guide/ instead; handovers are internal.


Known-broken — check before trusting numbers

The current set of known bugs and workarounds lives in docs/index.md (the “Open in the v0.7.x maintenance window” admonition) and CHANGELOG.md. It moves fast — read it there rather than trusting a list here. As of this writing the high-impact open items are the dense-crystal periodic-XC error on overlapping-image ionic cells, the incomplete CASSCF analytic nuclear gradient, and the gated GAPW / slab-Ewald / BIPOLE-gradient numerical issues tracked in handovers/HANDOVER_OPEN_BUGS_V014.md. The old f-shell RHF-gradient and current-build DF l >= 1 auxiliary-kernel failures are fixed and have active regression coverage (a stale libint install can still reproduce the historical DF crash). Always check the live inventory.


See also