{"playbook":"Software choices","url":"https://agentwranglers.se/playbook/stack","updated":"2026-10-21","license":"https://creativecommons.org/licenses/by/4.0/","credit":"Required by the licence: credit Agent Wranglers with a link to the rule or playbook you used.","how":"Each rule's How text is on its own page: add .md to its url.","rules":[{"chapter":"How to choose","name":"Pick software proven in production for five years","level":"Recommended","rule":"Choose software whose first stable release is at least five years old, so its failure modes are known and written about.","check":"Every runtime, framework, database and direct dependency had its first stable release at least five years ago. A decision record covers each exception.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/proven-for-five-years","related":["/playbook/engineering/documentation/decision-records","/playbook/stack/testing-and-code-quality/jest"],"sources":[]},{"chapter":"How to choose","name":"Pick software that still ships releases","level":"Required","risk":"takeover","rule":"Choose software with a release in the last six months, and use a version that is still inside its published support window.","check":"A script lists every runtime and direct dependency with its latest release date and support end. None is older than six months, and none of your versions is past its support end.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/still-maintained","related":["/playbook/infrastructure/servers/automatic-security-updates","/playbook/stack/languages-and-runtimes/nodejs-lts","/playbook/stack/languages-and-runtimes/yarn-classic","/playbook/stack/web-frameworks/create-react-app","/playbook/stack/web-frameworks/angularjs","/playbook/stack/testing-and-code-quality/retired-test-tools"],"sources":[]},{"chapter":"How to choose","name":"Pick what most teams already use","level":"Recommended","rule":"Choose one of the three most used options in its category, so hiring, answers and integrations are easy to find.","check":"Each pick is in the top three of its category in the latest Stack Overflow Developer Survey. Or it has over one million weekly downloads on npm or PyPI.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/widely-used","related":["/playbook/engineering/documentation/decision-records"],"sources":[]},{"chapter":"How to choose","name":"Pick what AI tools already know","level":"Recommended","rule":"Choose software that AI coding tools write correctly without extra documentation, and stay one major version behind the newest for six months after its release.","check":"Two different AI coding assistants each build the pick's official getting-started example with no documentation pasted in. Both results run and pass the example's own test on the first or second try.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/known-to-ai-tools","related":["/playbook/ai-assisted-development/setting-up-the-project/instructions-file","/playbook/stack/web-frameworks/react","/playbook/stack/operations-tools/opentelemetry"],"sources":[]},{"chapter":"How to choose","name":"Pick software a new developer learns alone","level":"Recommended","rule":"Choose software whose own documentation takes a new developer from nothing to a working example in one working day.","check":"A developer new to the tool finishes its official getting-started guide in under one working day and records the time.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/easy-to-learn","related":["/playbook/engineering/documentation/readme-quick-start"],"sources":[]},{"chapter":"How to choose","name":"Prefer open source and pay only when nothing open does the job","level":"Recommended","rule":"Pick open source software when an open source option passes the other tests. Pick a paid or proprietary service only when no open source option realistically does the job, and say why.","check":"Every dependency and service in the project has a cost label of Open source, Source available, Free service, Free tier or Paid. Each one not labelled Open source has a decision record naming the open source options it beat.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/prefer-open-source","related":["/playbook/engineering/documentation/decision-records"],"sources":[{"label":"OSI Approved Licenses","url":"https://opensource.org/licenses"}]},{"chapter":"How to choose","name":"Keep an open licence or a way out","level":"Recommended","rule":"Use software under an OSI-approved open source licence, or a managed service that exports all your data in a documented format.","check":"A licence checker in the build passes with an allow list of OSI-approved licences. Every managed service has a tested export, run in the last 12 months.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/open-licence-or-exit","related":["/playbook/adversarial-testing/supply-chain-and-setup/audit-dependencies","/playbook/stack/databases-and-storage/avoid-mongodb-default","/playbook/stack/delivery-and-infrastructure/avoid-busl-hashicorp"],"sources":[{"label":"OSI Approved Licenses","url":"https://opensource.org/licenses"},{"label":"SPDX License List","url":"https://spdx.org/licenses/"}]},{"chapter":"How to choose","name":"Keep one tool for each job","level":"Recommended","rule":"Keep one tool for each job, such as one test runner, one formatter and one database. Remove the old one when you switch.","check":"A script compares the dependency list with a list of categories, such as test runners, formatters, ORMs and HTTP clients. No category has more than one entry.","url":"https://agentwranglers.se/playbook/stack/how-to-choose/one-tool-per-job","related":["/playbook/ai-assisted-development/setting-up-the-project/instructions-file"],"sources":[]},{"chapter":"Languages and runtimes","name":"Use TypeScript for web apps and APIs","level":"Recommended","rule":"Write browser code and server code in TypeScript with strict mode on, so one language and one set of types covers the whole app.","check":"`npx tsc --version` prints 6.0 or 7.0. `tsconfig.json` sets `\"strict\": true`. `npx tsc --noEmit` exits with code 0 in continuous integration.","cost":"Open source. Apache-2.0 licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/typescript","related":["/playbook/stack/testing-and-code-quality/typescript-strict"],"sources":[{"label":"TypeScript documentation","url":"https://www.typescriptlang.org/docs/"},{"label":"TypeScript tsconfig reference","url":"https://www.typescriptlang.org/tsconfig/"}]},{"chapter":"Languages and runtimes","name":"Use Node.js on an LTS line","level":"Recommended","rule":"Run JavaScript and TypeScript servers on an even-numbered Node.js line that is in Active or Maintenance LTS, and move up before its end date.","check":"`node --version` prints v24 or v26 in production and in continuous integration. The `engines` field in `package.json` and the base image name the same major version.","cost":"Open source. MIT licence, free to run on your own servers. Managed hosting from several vendors is paid.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/nodejs-lts","related":["/playbook/stack/how-to-choose/still-maintained"],"sources":[{"label":"Node.js releases","url":"https://nodejs.org/en/about/previous-releases"},{"label":"Node.js release schedule","url":"https://github.com/nodejs/Release"}]},{"chapter":"Languages and runtimes","name":"Use Python for data work and as a second server language","level":"Recommended","rule":"Use a Python version in its bugfix phase for scripts, data jobs and APIs, and keep each project on one version.","check":"`python3 --version` prints 3.14 or 3.15. `requires-python` in `pyproject.toml` names the same version. No server runs 3.10 or older.","cost":"Open source. PSF licence, free to use and ship in any project.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/python","related":["/playbook/stack/testing-and-code-quality/pyright"],"sources":[{"label":"Status of Python versions","url":"https://devguide.python.org/versions/"},{"label":"Python downloads","url":"https://www.python.org/downloads/"}]},{"chapter":"Languages and runtimes","name":"Use npm with a lock file for JavaScript packages","level":"Recommended","rule":"Use the npm that ships with your Node.js line, commit `package-lock.json`, and install with `npm ci` in builds.","check":"`package-lock.json` is committed. Every build runs `npm ci`, never `npm install`. The repository has no `yarn.lock` or second lock file.","cost":"Open source. The npm command is Artistic-2.0 licensed and the public registry is free. Private packages on the registry are paid.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/npm","related":["/playbook/adversarial-testing/supply-chain-and-setup/audit-dependencies"],"sources":[{"label":"npm ci","url":"https://docs.npmjs.com/cli/commands/npm-ci"},{"label":"pnpm","url":"https://pnpm.io/"}]},{"chapter":"Languages and runtimes","name":"Use pip and a virtual environment for Python packages","level":"Recommended","rule":"Install Python packages with pip into a virtual environment per project, from a file that pins every version.","check":"The project has a `.venv` folder that is not committed. `requirements.txt` pins every package with `==`. Every build runs `pip install -r requirements.txt` inside the virtual environment.","cost":"Open source. MIT licence, and the Python Package Index is free to use.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/pip-and-venv","related":["/playbook/engineering/running-in-production/reproducible-builds"],"sources":[{"label":"pip documentation","url":"https://pip.pypa.io/"},{"label":"venv","url":"https://docs.python.org/3/library/venv.html"}]},{"chapter":"Languages and runtimes","name":"Learn SQL and write it with parameters","level":"Recommended","rule":"Every developer on a project with a database reads and writes plain SQL, even when the code uses a query builder or an ORM.","check":"A search of the code finds no query built by joining strings with user input. Every schema change is a SQL migration file in version control, as [versioned migrations](https://agentwranglers.se/playbook/data/migrations/versioned-migrations) requires.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/sql","related":["/playbook/adversarial-testing/injection/sql-injection","/playbook/data/queries-and-indexes/query-plans","/playbook/data/migrations/versioned-migrations"],"sources":[]},{"chapter":"Languages and runtimes","name":"Leave runtimes that are out of support","level":"Avoid","rule":"Never run a Node.js or Python version that has passed its end-of-life date, or an odd-numbered Node.js line in production.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/end-of-life-runtimes","related":["/playbook/engineering/version-control-and-review/automated-dependency-updates"],"sources":[{"label":"Node.js release schedule","url":"https://github.com/nodejs/Release"},{"label":"Status of Python versions","url":"https://devguide.python.org/versions/"}]},{"chapter":"Languages and runtimes","name":"Leave Yarn Classic","level":"Avoid","rule":"Never start a project on Yarn 1, and move existing ones to npm or pnpm.","url":"https://agentwranglers.se/playbook/stack/languages-and-runtimes/yarn-classic","related":["/playbook/stack/how-to-choose/still-maintained"],"sources":[{"label":"Yarn","url":"https://yarnpkg.com/"}]},{"chapter":"Web frameworks","name":"Use React for the front end","level":"Recommended","rule":"Build interactive screens with React, on a version that has been out for at least six months.","check":"package.json lists react and react-dom at the same major version, and that major was released at least six months ago.","cost":"Open source. MIT licence, free to use in any app.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/react","related":["/playbook/stack/how-to-choose/known-to-ai-tools","/playbook/stack/web-frameworks/vite"],"sources":[{"label":"React","url":"https://react.dev/learn"},{"label":"React versions","url":"https://react.dev/versions"}]},{"chapter":"Web frameworks","name":"Use Vite to build and serve the front end","level":"Recommended","rule":"Use Vite as the development server and build tool for any front end that is not inside a full-stack framework.","check":"The project has one build tool in package.json, and its Vite version is inside the supported list on the Vite releases page.","cost":"Open source. MIT licence, free to run on your own machines and build servers.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/vite","related":["/playbook/stack/web-frameworks/react"],"sources":[{"label":"Vite guide","url":"https://vite.dev/guide/"},{"label":"Vite releases","url":"https://vite.dev/releases"}]},{"chapter":"Web frameworks","name":"Use Next.js when React pages need a server","level":"Recommended","rule":"Use Next.js when a React app needs pages rendered on the server, search engine traffic or server code next to the screens.","check":"The installed Next.js major is listed as Active LTS or Maintenance LTS on the Next.js support policy page, and `next build` passes in the build.","cost":"Open source. MIT licence, and it runs on any Node server. Hosting on Vercel, the company behind it, is optional and paid.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/nextjs","related":["/playbook/website/seo/server-side-rendering"],"sources":[{"label":"Next.js docs","url":"https://nextjs.org/docs"},{"label":"Next.js support policy","url":"https://nextjs.org/support-policy"}]},{"chapter":"Web frameworks","name":"Use Express for a Node.js server","level":"Recommended","rule":"Use Express 5 for HTTP servers and APIs in Node.js.","check":"package.json lists express at major version 5 or higher, and no route file wraps async handlers in a try and catch only to call next.","cost":"Open source. MIT licence, free to run on your own servers.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/express","related":["/playbook/stack/web-frameworks/zod-pydantic"],"sources":[{"label":"Express","url":"https://expressjs.com/"},{"label":"Express version support","url":"https://expressjs.com/en/support/"}]},{"chapter":"Web frameworks","name":"Use Django for a Python web app","level":"Recommended","rule":"Use Django for a Python web app with a database, user accounts and an admin, on its current long-term support release.","check":"The installed Django release series appears in the supported versions table on the Django download page, with an end date at least six months away.","cost":"Open source. BSD-3-Clause licence, free to run on your own servers.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/django","related":["/playbook/data/migrations/versioned-migrations"],"sources":[{"label":"Django download and supported versions","url":"https://www.djangoproject.com/download/"}]},{"chapter":"Web frameworks","name":"Use FastAPI for a Python API","level":"Optional","rule":"Use FastAPI for a Python service that only answers JSON, and pin its minor version.","check":"requirements or pyproject pins FastAPI to one minor version, such as `fastapi>=0.156,<0.157`, and a decision record names the exception.","cost":"Open source. MIT licence, free to run on your own servers.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/fastapi","related":["/playbook/stack/web-frameworks/zod-pydantic"],"sources":[{"label":"FastAPI","url":"https://fastapi.tiangolo.com/"}]},{"chapter":"Web frameworks","name":"Use Zod or Pydantic to check every input","level":"Recommended","rule":"Parse every request body, query string, webhook and file at the boundary with a schema library, Zod in TypeScript or Pydantic in Python.","check":"Every route handler reads its input through a schema parse call. A test sends each route one unknown field and one wrong type, and gets 400 or 422.","cost":"Open source. Both are MIT licensed and free to use. Pydantic's company sells separate paid products that you do not need.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/zod-pydantic","related":["/playbook/stack/web-frameworks/express","/playbook/stack/web-frameworks/fastapi","/playbook/api-design/requests-and-responses/strict-input"],"sources":[{"label":"Zod","url":"https://zod.dev/"},{"label":"Pydantic","url":"https://pydantic.dev/docs/validation/latest/get-started/"}]},{"chapter":"Web frameworks","name":"Use plain CSS before adding a CSS framework","level":"Recommended","rule":"Write styles in plain CSS files with custom properties, and add a utility framework only when the team already writes it.","check":"The project has at most one CSS tool in package.json, and every colour value in the stylesheets is a custom property defined in one file.","cost":"Open source. CSS is an open web standard with no licence fee. Tailwind CSS is MIT licensed.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/plain-css","related":["/playbook/app-ui/styles-and-components/design-tokens"],"sources":[{"label":"W3C CSS current work","url":"https://www.w3.org/Style/CSS/current-work"},{"label":"Tailwind CSS","url":"https://tailwindcss.com/docs"}]},{"chapter":"Web frameworks","name":"Avoid Create React App","level":"Avoid","rule":"Do not start or keep an app on Create React App, the react-scripts package.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/create-react-app","related":["/playbook/stack/how-to-choose/still-maintained"],"sources":[{"label":"Sunsetting Create React App","url":"https://react.dev/blog/2025/02/14/sunsetting-create-react-app"}]},{"chapter":"Web frameworks","name":"Avoid AngularJS","level":"Avoid","rule":"Do not build on AngularJS, the 1.x framework in the angular package. Its successor, Angular, is a different framework.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/angularjs","related":["/playbook/stack/how-to-choose/still-maintained"],"sources":[{"label":"AngularJS support status","url":"https://docs.angularjs.org/misc/version-support-status"},{"label":"Angular","url":"https://angular.dev/"}]},{"chapter":"Web frameworks","name":"Use DOMPurify or nh3 to clean user HTML","level":"Recommended","rule":"Clean any HTML that users write with DOMPurify in JavaScript or nh3 in Python, with a short allow list of tags and attributes.","check":"A test cleans `<img src=x onerror=alert(1)>` and `<a href=\"javascript:alert(1)\">x</a>`. Neither the `onerror` attribute nor the `javascript:` link is in the output. The project has no dependency on bleach.","cost":"Open source. DOMPurify is Apache-2.0 or MPL-2.0, and nh3 is MIT. Both are free to use in any project.","url":"https://agentwranglers.se/playbook/stack/web-frameworks/html-sanitiser","related":["/playbook/adversarial-testing/injection/cross-site-scripting"],"sources":[{"label":"OWASP Cross Site Scripting Prevention Cheat Sheet","url":"https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"},{"label":"DOMPurify","url":"https://github.com/cure53/DOMPurify"},{"label":"nh3","url":"https://nh3.readthedocs.io/"}]},{"chapter":"Databases and storage","name":"Use PostgreSQL for the main database","level":"Recommended","rule":"Keep the main business data of a web app in PostgreSQL, on a major version with more than 12 months of support left.","check":"`SHOW server_version` on every environment returns the current minor release of a major version. The versioning page lists that major's end of life more than 12 months after today.","cost":"Open source. PostgreSQL Licence, free to run on your own servers. Managed hosting from every large cloud is paid.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/postgresql","related":["/playbook/data/operations/connection-pooling","/playbook/data/backups-and-recovery/point-in-time-recovery"],"sources":[{"label":"PostgreSQL versioning policy","url":"https://www.postgresql.org/support/versioning/"},{"label":"PostgreSQL licence","url":"https://www.postgresql.org/about/licence/"}]},{"chapter":"Databases and storage","name":"Use SQLite for a small or embedded database","level":"Recommended","rule":"Use SQLite when one server process owns the data. Examples are a small site, a phone app, a test suite or a local tool.","check":"Only one server process opens the database file for writing. `PRAGMA journal_mode` returns `wal`, and `PRAGMA busy_timeout` returns at least 5000.","cost":"Open source. Public domain, free to copy and ship inside any app.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/sqlite","related":["/playbook/data/backups-and-recovery/restore-tests"],"sources":[{"label":"SQLite long-term support","url":"https://www.sqlite.org/lts.html"},{"label":"When to use SQLite","url":"https://www.sqlite.org/whentouse.html"}]},{"chapter":"Databases and storage","name":"Use Redis 8 or Valkey for caches and queues","level":"Recommended","rule":"Run Redis 8 or later, or Valkey, for caches, rate limits and job queues. Never run Redis 7.4 to 7.8, whose licences are not open source.","check":"`INFO server` on every instance reports Valkey, or Redis 8.0 or later. No instance reports a Redis version from 7.4 to 7.8.","cost":"Open source. Redis 8 offers AGPLv3 and Valkey is BSD-3-Clause. Managed hosting is paid.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/redis-or-valkey","related":["/playbook/data/caches-and-copies/cache-freshness"],"sources":[{"label":"Redis licences","url":"https://redis.io/legal/licenses/"},{"label":"Valkey","url":"https://valkey.io/"}]},{"chapter":"Databases and storage","name":"Use a mature ORM for database code","level":"Recommended","rule":"Write database code with Prisma in TypeScript and SQLAlchemy in Python, or with the Django ORM inside a Django project. Keep one ORM per codebase.","check":"The dependency list holds exactly one of `@prisma/client`, `drizzle-orm`, `typeorm`, `sequelize`, `sqlalchemy` or the Django ORM. No source file builds SQL by joining strings with user input.","cost":"Open source. Prisma is Apache-2.0, SQLAlchemy is MIT and Django is BSD-3-Clause. Prisma also sells paid hosted add-ons, which the ORM does not need.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/orm","related":["/playbook/data/queries-and-indexes/parameters","/playbook/data/queries-and-indexes/no-n-plus-one"],"sources":[{"label":"Prisma ORM docs","url":"https://www.prisma.io/docs/orm"},{"label":"SQLAlchemy","url":"https://www.sqlalchemy.org/"},{"label":"Django releases","url":"https://www.djangoproject.com/download/"}]},{"chapter":"Databases and storage","name":"Use the migration tool that comes with your ORM","level":"Recommended","rule":"Change the schema with the migration tool of the ORM you use, which is Prisma Migrate, Alembic for SQLAlchemy or Django migrations.","check":"The build runs the tool's drift check and fails when the models and migrations differ. The commands are `prisma migrate diff --exit-code`, `alembic check` and `python manage.py makemigrations --check`.","cost":"Open source. Each tool ships under its ORM's licence, free to run anywhere.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/migration-tool","related":["/playbook/data/migrations/versioned-migrations","/playbook/data/migrations/test-migrations"],"sources":[{"label":"Alembic","url":"https://alembic.sqlalchemy.org/"},{"label":"Prisma Migrate","url":"https://www.prisma.io/docs/orm/prisma-migrate"}]},{"chapter":"Databases and storage","name":"Use PostgreSQL search first and OpenSearch when it falls short","level":"Recommended","rule":"Build search on PostgreSQL full-text search first. Add OpenSearch only when a measured need fails in PostgreSQL.","check":"Search queries in PostgreSQL use a GIN index and answer within 200 ms at the 95th percentile. A decision record names the failed measure for every second search engine in use.","cost":"Open source. PostgreSQL search is built in, and OpenSearch is Apache-2.0. Managed OpenSearch hosting is paid.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/full-text-search","related":["/playbook/data/caches-and-copies/search-index-sync"],"sources":[{"label":"PostgreSQL text search","url":"https://www.postgresql.org/docs/current/textsearch.html"},{"label":"OpenSearch releases","url":"https://opensearch.org/releases/"}]},{"chapter":"Databases and storage","name":"Use the S3 API for files and SeaweedFS to host them","level":"Recommended","rule":"Keep uploads and other files in object storage reached through the S3 API, with the endpoint and bucket in settings. Host it yourself on SeaweedFS.","check":"The code reaches storage only through an S3 client whose endpoint and bucket come from settings. The upload test passes against a second S3-compatible endpoint.","cost":"Open source. The S3 clients and SeaweedFS are Apache-2.0. A cloud object store is paid, and SeaweedFS also sells a paid enterprise edition.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/s3-api","related":["/playbook/stack/databases-and-storage/avoid-minio-community"],"sources":[{"label":"Amazon S3 API reference","url":"https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html"},{"label":"SeaweedFS","url":"https://github.com/seaweedfs/seaweedfs"}]},{"chapter":"Databases and storage","name":"Use BullMQ or RQ for background jobs","level":"Recommended","rule":"Run slow or retried work in background jobs with BullMQ in Node.js or RQ in Python, both on Redis or Valkey.","check":"Every job type sets at most 5 attempts with a growing delay between them. Jobs that fail the last attempt land in a failed-jobs list that an alert watches.","cost":"Open source. BullMQ is MIT and RQ is BSD-2-Clause. BullMQ's maker also sells a paid edition with extra features.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/background-jobs","related":["/playbook/data/transactions-and-concurrency/idempotent-writes","/playbook/data/transactions-and-concurrency/dead-letter-queue"],"sources":[{"label":"BullMQ docs","url":"https://docs.bullmq.io/"},{"label":"RQ docs","url":"https://python-rq.org/"}]},{"chapter":"Databases and storage","name":"Avoid MongoDB as the default database","level":"Avoid","rule":"Do not pick MongoDB Community Server as the main database of a new web app.","cost":"Source available. The SSPL lets you read the code, but the OSI has not approved it.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/avoid-mongodb-default","related":["/playbook/stack/how-to-choose/open-licence-or-exit"],"sources":[{"label":"MongoDB SSPL FAQ","url":"https://www.mongodb.com/legal/licensing/server-side-public-license/faq"}]},{"chapter":"Databases and storage","name":"Avoid the MinIO community edition","level":"Avoid","rule":"Do not start new self-hosted object storage on the MinIO community edition, and plan a move off it.","cost":"Open source. AGPLv3, but no longer maintained.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/avoid-minio-community","related":["/playbook/stack/databases-and-storage/s3-api"],"sources":[{"label":"MinIO repository","url":"https://github.com/minio/minio"}]},{"chapter":"Databases and storage","name":"Use sharp or Pillow to process images","level":"Recommended","rule":"Resize, turn upright and re-encode images with sharp in Node.js or Pillow in Python, and use no other image library.","check":"A test rebuilds a photo that carries GPS tags. The output has no EXIF data. The lock file holds one image library per language.","cost":"Open source. sharp is Apache-2.0 and Pillow is MIT-CMU. Both are free to use in any project.","url":"https://agentwranglers.se/playbook/stack/databases-and-storage/image-processing","related":["/playbook/api-design/files-and-uploads/process-images"],"sources":[{"label":"sharp output options","url":"https://sharp.pixelplumbing.com/api-output/"},{"label":"Pillow","url":"https://pillow.readthedocs.io/en/stable/"}]},{"chapter":"Testing and code quality","name":"Use pytest for Python tests","level":"Recommended","rule":"Write every Python test as a plain function run by pytest, with fixtures for shared setup.","check":"`pytest --collect-only -q` finds every test file, and the project has no second Python test runner in its dependencies.","cost":"Open source. MIT licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/pytest","related":["/playbook/testing/unit-tests/fast-unit-tests"],"sources":[{"label":"pytest documentation","url":"https://docs.pytest.org/"}]},{"chapter":"Testing and code quality","name":"Use Jest for JavaScript unit tests","level":"Recommended","rule":"Run JavaScript and TypeScript unit tests with Jest, with one config file at the project root.","check":"`npx jest --listTests` lists every test file, and `package.json` lists only one of `jest`, `vitest` and `mocha`.","cost":"Open source. MIT licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/jest","related":["/playbook/stack/how-to-choose/proven-for-five-years","/playbook/stack/ai-software/evals-in-test-runner"],"sources":[{"label":"Jest documentation","url":"https://jestjs.io/docs/getting-started"}]},{"chapter":"Testing and code quality","name":"Use Playwright for browser tests","level":"Recommended","rule":"Drive browser tests with Playwright, in Chromium, Firefox and WebKit, against the built app.","check":"The browser tests run in Chromium, Firefox and WebKit, as [Test in more than one browser and size](https://agentwranglers.se/playbook/testing/end-to-end-and-browser-tests/browsers-and-sizes) asks. No test calls `waitForTimeout`.","cost":"Open source. Apache-2.0 licence, free to use in any project. Paid cloud browser grids are optional.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/playwright","related":["/playbook/testing/end-to-end-and-browser-tests/no-sleeps","/playbook/testing/end-to-end-and-browser-tests/browsers-and-sizes","/playbook/stack/product-services/html-to-pdf"],"sources":[{"label":"Playwright","url":"https://playwright.dev/"}]},{"chapter":"Testing and code quality","name":"Use axe-core for accessibility tests","level":"Recommended","rule":"Run axe-core on every page your browser tests open, and fail the test on any violation.","check":"Every page the browser tests open runs axe-core with the `wcag2a`, `wcag2aa` and `wcag21aa` tags, and the build fails on one violation.","cost":"Open source. MPL-2.0 licence, free to use in any project. Deque sells paid tools built on it.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/axe-core","related":["/playbook/testing/non-functional-tests/accessibility-in-ci"],"sources":[{"label":"axe-core","url":"https://github.com/dequelabs/axe-core"}]},{"chapter":"Testing and code quality","name":"Use ESLint and Prettier for JavaScript code","level":"Recommended","rule":"Lint JavaScript and TypeScript with ESLint and format it with Prettier, and fail the build on either.","check":"`npx eslint .` and `npx prettier --check .` both exit 0 in the build, and the project has no second formatter.","cost":"Open source. Both are MIT licensed and free to use in any project.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/eslint-and-prettier","related":["/playbook/architecture/structure/one-formatter"],"sources":[{"label":"ESLint","url":"https://eslint.org/"},{"label":"Prettier","url":"https://prettier.io/"}]},{"chapter":"Testing and code quality","name":"Use Ruff to lint and format Python","level":"Recommended","rule":"Lint and format Python with Ruff, in one config section, and fail the build on either.","check":"`ruff check .` and `ruff format --check .` both exit 0 in the build, and no other Python linter or formatter is in the dependencies.","cost":"Open source. MIT licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/ruff","related":["/playbook/architecture/structure/one-formatter"],"sources":[{"label":"Ruff","url":"https://docs.astral.sh/ruff/"}]},{"chapter":"Testing and code quality","name":"Use TypeScript in strict mode","level":"Recommended","rule":"Write JavaScript projects in TypeScript with `strict` on, and run `tsc --noEmit` in the build.","check":"`tsconfig.json` has no `\"strict\": false` and no strict sub-flag turned off. `npx tsc --noEmit` exits 0 in the build.","cost":"Open source. Apache-2.0 licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/typescript-strict","related":["/playbook/stack/languages-and-runtimes/typescript","/playbook/architecture/data-shapes/no-mixed-or-any"],"sources":[{"label":"TSConfig strict","url":"https://www.typescriptlang.org/tsconfig/#strict"},{"label":"Announcing TypeScript 7.0","url":"https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/"}]},{"chapter":"Testing and code quality","name":"Use Pyright to check Python types","level":"Recommended","rule":"Add type hints to Python code and check them with Pyright in strict mode for new code.","check":"`pyright` exits 0 in the build, and `pyproject.toml` sets `typeCheckingMode = \"strict\"` for every new package.","cost":"Open source. MIT licence, free to use in any project. Pylance, its editor extension, is not open source.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/pyright","related":["/playbook/stack/languages-and-runtimes/python","/playbook/ai-assisted-development/rules-the-agent-cannot-break/strict-tooling"],"sources":[{"label":"Pyright","url":"https://microsoft.github.io/pyright/"}]},{"chapter":"Testing and code quality","name":"Use Renovate for dependency updates","level":"Recommended","rule":"Let Renovate open dependency update pull requests, with one `renovate.json` at the repository root.","check":"The repository holds `renovate.json` and no `.github/dependabot.yml`, and the config covers every package manager and the CI workflow files.","cost":"Open source. AGPL-3.0 licence, free to run on your own servers or in CI. The hosted app from Mend is a free service.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/dependency-bot","related":["/playbook/engineering/version-control-and-review/automated-dependency-updates"],"sources":[{"label":"Renovate","url":"https://docs.renovatebot.com/"},{"label":"Dependabot","url":"https://docs.github.com/en/code-security/dependabot"}]},{"chapter":"Testing and code quality","name":"Avoid Karma, Protractor and TSLint","level":"Avoid","rule":"Do not start with Karma, Protractor or TSLint, and move existing projects off them.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/retired-test-tools","related":["/playbook/stack/how-to-choose/still-maintained"],"sources":[]},{"chapter":"Testing and code quality","name":"Use Semgrep for static security scans","level":"Recommended","rule":"Scan source code for security mistakes with Semgrep Community Edition in CI, with the rule sets named in the repository.","check":"Every pull request runs `semgrep scan --error` with named rule sets. A test file with SQL built from a formatted string makes the job fail.","cost":"Open source. The Semgrep Community Edition engine is LGPL-2.1. The rules Semgrep maintains are free for internal business use only.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/semgrep","related":["/playbook/adversarial-testing/rules-of-engagement/scan-every-change"],"sources":[{"label":"Semgrep licensing","url":"https://docs.semgrep.dev/licensing"}]},{"chapter":"Testing and code quality","name":"Use TruffleHog to find leaked secrets","level":"Recommended","rule":"Scan every pull request and the full Git history for secrets with TruffleHog, and fail the build on any verified or unknown finding.","check":"Every pull request runs `trufflehog git` with `--results=verified,unknown --fail`. A run over the full history exits 0.","cost":"Open source. AGPL-3.0 licence, free to run in your own builds.","url":"https://agentwranglers.se/playbook/stack/testing-and-code-quality/secret-scanner","related":["/playbook/adversarial-testing/data-and-secrets/secrets-out-of-code"],"sources":[{"label":"TruffleHog","url":"https://github.com/trufflesecurity/trufflehog"}]},{"chapter":"Delivery and infrastructure","name":"Use Git on GitHub for version control and code hosting","level":"Recommended","rule":"Keep every repository in Git, hosted on GitHub, with the default branch protected.","check":"`git --version` on every developer machine and CI runner prints a release from the last six months. A mirror clone of each repository to a second host finishes without errors, run in the last 12 months.","cost":"Free tier. Git is open source under GPL-2.0. GitHub is proprietary, free for public and small private projects and paid above that.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/git-on-github","related":["/playbook/engineering/version-control-and-review/protect-the-main-branch"],"sources":[{"label":"Git","url":"https://git-scm.com/"},{"label":"About protected branches","url":"https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches"}]},{"chapter":"Delivery and infrastructure","name":"Use GitHub Actions for continuous integration","level":"Recommended","rule":"Run every build, test and deploy in GitHub Actions workflows stored in the repository, with each step a plain shell command.","check":"Every workflow file runs one script per job, and the same scripts pass on a developer machine. Every third-party action is pinned to a full 40-character commit hash.","cost":"Free tier. Free for public repositories with a monthly quota of minutes for private ones, paid above that. Not open source.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/github-actions-ci","related":["/playbook/engineering/version-control-and-review/continuous-integration","/playbook/infrastructure/configuration-and-secrets/oidc-for-ci"],"sources":[{"label":"GitHub Actions documentation","url":"https://docs.github.com/en/actions"}]},{"chapter":"Delivery and infrastructure","name":"Use Docker and Docker Compose for containers","level":"Recommended","rule":"Package each service as a Docker image built from a Dockerfile, and describe the local stack in one Compose file.","check":"`docker compose up` starts the whole stack from a fresh clone with no other setup step. Every Dockerfile pins its base image by version and digest.","cost":"Open source. Docker Engine and Compose are Apache-2.0. Docker Desktop is paid for larger companies.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/docker-and-compose","related":["/playbook/operations/containers/compose-for-local","/playbook/operations/containers/pinned-base-images"],"sources":[{"label":"Docker documentation","url":"https://docs.docker.com/"},{"label":"Docker Desktop licence","url":"https://docs.docker.com/subscription/desktop-license/"}]},{"chapter":"Delivery and infrastructure","name":"Use OpenTofu for infrastructure as code","level":"Recommended","rule":"Describe all cloud resources in OpenTofu files kept in the repository, and apply them only from CI.","check":"`tofu plan` on the main branch shows zero changes. The `required_version` line pins OpenTofu to one minor version, such as `~> 1.13.0`.","cost":"Open source. MPL-2.0, free to run anywhere. Remote state storage in a cloud bucket is paid.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/opentofu","related":["/playbook/infrastructure/infrastructure-as-code/remote-state"],"sources":[{"label":"OpenTofu","url":"https://opentofu.org/"},{"label":"OpenTofu 1.6.0 is generally available","url":"https://opentofu.org/blog/opentofu-is-going-ga/"}]},{"chapter":"Delivery and infrastructure","name":"Avoid HashiCorp tools under the Business Source License for new work","level":"Avoid","rule":"Do not start new projects on Terraform or Vault releases under the Business Source License. Use OpenTofu and OpenBao, their open source forks.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/avoid-busl-hashicorp","related":["/playbook/stack/how-to-choose/open-licence-or-exit"],"sources":[{"label":"HashiCorp adopts the Business Source License","url":"https://www.hashicorp.com/en/blog/hashicorp-adopts-business-source-license"},{"label":"OSI approved licences","url":"https://opensource.org/licenses"}]},{"chapter":"Delivery and infrastructure","name":"Use Ubuntu LTS or Debian stable on servers","level":"Recommended","rule":"Run servers and container base images on a Ubuntu LTS release or Debian stable that is inside its standard security support.","check":"`grep VERSION_ID /etc/os-release` on every server and base image names a release inside its standard support window. No server runs a non-LTS Ubuntu release.","cost":"Open source. Both are free to run. Ubuntu Pro adds paid extended support.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/ubuntu-lts-or-debian","related":["/playbook/infrastructure/servers/automatic-security-updates"],"sources":[{"label":"Ubuntu release cycle","url":"https://ubuntu.com/about/release-cycle"},{"label":"Debian 13 trixie","url":"https://www.debian.org/releases/trixie/"}]},{"chapter":"Delivery and infrastructure","name":"Use nginx or Caddy as the web server and reverse proxy","level":"Recommended","rule":"Put nginx or Caddy in front of every app server to serve TLS, static files, compression and redirects.","check":"`nginx -v` or `caddy version` prints a release from the last six months. `curl -sI https://example.com` returns a `strict-transport-security` header.","cost":"Open source. nginx is BSD-2-Clause and Caddy is Apache-2.0. A commercial nginx edition is paid.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/nginx-or-caddy","related":["/playbook/website/security/content-security-policy"],"sources":[{"label":"nginx","url":"https://nginx.org/"},{"label":"Caddy reverse proxy","url":"https://caddyserver.com/docs/quick-starts/reverse-proxy"}]},{"chapter":"Delivery and infrastructure","name":"Use Let's Encrypt for TLS certificates","level":"Recommended","rule":"Get every public certificate from Let's Encrypt through an ACME client that renews it by itself, such as certbot or Caddy.","check":"`certbot renew --dry-run` passes, or the Caddy log shows a renewal. Every public certificate has 30 days or more left, checked by a monitor every day.","cost":"Free service. Certificates cost nothing and certbot is open source under Apache-2.0.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/lets-encrypt","related":["/playbook/infrastructure/networking-and-dns/enforce-tls-on-services"],"sources":[{"label":"Let's Encrypt certificate lifetimes","url":"https://letsencrypt.org/docs/cert-lifetimes/"},{"label":"ACME, RFC 8555","url":"https://www.rfc-editor.org/rfc/rfc8555"}]},{"chapter":"Delivery and infrastructure","name":"Use a managed DNS and CDN that exports your zone","level":"Recommended","rule":"Host DNS and the CDN with a managed provider that exports the zone as a standard zone file, such as Cloudflare.","check":"A zone export through the provider's API returns every record, run in the last 12 months. The origin server answers on its own address with the CDN switched off.","cost":"Free tier. Cloudflare has a free plan for DNS and CDN, paid above that. Not open source.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/managed-dns-and-cdn","related":["/playbook/infrastructure/networking-and-dns/dns-as-code"],"sources":[{"label":"Import and export DNS records","url":"https://developers.cloudflare.com/dns/manage-dns-records/how-to/import-and-export/"}]},{"chapter":"Delivery and infrastructure","name":"Use SOPS for secrets","level":"Recommended","rule":"Keep runtime secrets in the repository encrypted with SOPS, and decrypt them only on the server or in CI.","check":"A secret scanner such as TruffleHog or gitleaks finds zero plain-text secrets in the full Git history. Every secret file in the repository opens only with `sops --decrypt`.","cost":"Open source. SOPS is MPL-2.0. A cloud secrets manager is paid per secret and per request.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/secrets-manager-or-sops","related":["/playbook/engineering/running-in-production/secrets-store"],"sources":[{"label":"SOPS","url":"https://getsops.io/"},{"label":"OpenBao","url":"https://openbao.org/"}]},{"chapter":"Delivery and infrastructure","name":"Use Trivy to scan images and dependencies","level":"Recommended","rule":"Scan every container image, lock file and infrastructure file with Trivy in CI, and pin Trivy itself by image digest or full commit hash.","check":"Every CI step that runs Trivy names it by image digest or full commit hash, never by tag. The image scan exits non-zero on a critical finding that has a fix.","cost":"Open source. Apache-2.0 licence, free to run in any build.","url":"https://agentwranglers.se/playbook/stack/delivery-and-infrastructure/trivy","related":["/playbook/operations/containers/image-scanning"],"sources":[{"label":"CVE-2026-33634 Trivy supply chain compromise","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33634"},{"label":"Trivy","url":"https://trivy.dev/docs/latest/"}]},{"chapter":"Operations tools","name":"Use OpenTelemetry to instrument your code","level":"Recommended","rule":"Add traces, metrics and log context to your code with the OpenTelemetry API and SDK, and send them over OTLP to any backend.","check":"Every service starts the OpenTelemetry SDK at boot and exports over OTLP. A test request shows one trace with spans from at least 2 services in the backend.","cost":"Open source. Apache-2.0 licence, free to use with any backend. Managed backends that store the data are paid.","url":"https://agentwranglers.se/playbook/stack/operations-tools/opentelemetry","related":["/playbook/stack/how-to-choose/known-to-ai-tools","/playbook/operations/metrics-and-traces/distributed-tracing"],"sources":[{"label":"OpenTelemetry language APIs and SDKs","url":"https://opentelemetry.io/docs/languages/"},{"label":"OpenTelemetry status","url":"https://opentelemetry.io/status/"}]},{"chapter":"Operations tools","name":"Use Prometheus for metrics","level":"Recommended","rule":"Store and query numeric metrics with Prometheus, and alert on them with its rules and Alertmanager.","check":"Every service has a `/metrics` endpoint or pushes OTLP metrics. The Prometheus targets page shows 0 targets down, and `promtool check rules` passes in the build.","cost":"Open source. Apache-2.0 licence, free to run on your own servers. Managed hosting from several vendors is paid.","url":"https://agentwranglers.se/playbook/stack/operations-tools/prometheus","related":["/playbook/operations/metrics-and-traces/red-metrics","/playbook/operations/alerts-and-on-call/alert-on-symptoms"],"sources":[{"label":"Prometheus release cycle","url":"https://prometheus.io/docs/introduction/release-cycle/"},{"label":"Prometheus overview","url":"https://prometheus.io/docs/introduction/overview/"}]},{"chapter":"Operations tools","name":"Use Grafana for dashboards","level":"Recommended","rule":"Build dashboards in Grafana, with each dashboard saved as JSON in the repository.","check":"Every dashboard in production has a JSON file in the repository, loaded by provisioning. A script compares both lists and finds 0 dashboards that exist only in the UI.","cost":"Open source. AGPLv3 licence, free to run on your own servers. Grafana Cloud has a free tier and paid plans.","url":"https://agentwranglers.se/playbook/stack/operations-tools/grafana","related":["/playbook/operations/metrics-and-traces/service-dashboards"],"sources":[{"label":"Grafana licensing","url":"https://grafana.com/licensing/"},{"label":"Provision Grafana","url":"https://grafana.com/docs/grafana/latest/administration/provisioning/"}]},{"chapter":"Operations tools","name":"Use JSON logs and Loki to store them","level":"Recommended","rule":"Write one JSON object per log line to standard output, and ship the lines to Loki or another store that searches by label.","check":"A script reads 100 lines of production log output, and every line parses as JSON with a time, level and message. A query in the store finds a test line within 60 seconds.","cost":"Open source. Pino and Loki are free to run on your own servers. Managed log storage from several vendors is paid.","url":"https://agentwranglers.se/playbook/stack/operations-tools/json-logs-and-loki","related":["/playbook/operations/logs/structured-logs"],"sources":[{"label":"Grafana Loki documentation","url":"https://grafana.com/docs/loki/latest/"},{"label":"Grafana licensing","url":"https://grafana.com/licensing/"}]},{"chapter":"Operations tools","name":"Use GlitchTip for error tracking","level":"Recommended","rule":"Send every unhandled server and browser error to GlitchTip through the Sentry SDKs, tagged with the release. Fix new errors before the next deploy.","check":"A test error thrown in production after a deploy shows up in GlitchTip within 60 seconds, with the release tag and a readable stack trace.","cost":"Open source. MIT licence, free to run on your own servers. Hosted GlitchTip has a free tier and paid plans.","url":"https://agentwranglers.se/playbook/stack/operations-tools/glitchtip","related":["/playbook/operations/logs/logs-without-personal-data"],"sources":[{"label":"GlitchTip 1.0","url":"https://glitchtip.com/blog/2020-07-15-glitchtip-1-0-is-now-available"},{"label":"Install GlitchTip","url":"https://glitchtip.com/documentation/install"},{"label":"GlitchTip source","url":"https://gitlab.com/glitchtip/glitchtip-backend"}]},{"chapter":"Operations tools","name":"Use Sentry only for features GlitchTip lacks","level":"Optional","rule":"Move from GlitchTip to Sentry only when you need a Sentry feature GlitchTip lacks, and name that feature in a decision record.","check":"A decision record names the Sentry feature GlitchTip lacks. A test error thrown in production shows up in Sentry within 60 seconds, with the release tag.","cost":"Source available. The server is under FSL, not an open source licence. The hosted service has a free tier and paid plans.","url":"https://agentwranglers.se/playbook/stack/operations-tools/sentry","related":["/playbook/website/when-things-break/browser-error-reports"],"sources":[{"label":"Sentry licensing","url":"https://open.sentry.io/licensing/"},{"label":"Self-hosted Sentry","url":"https://develop.sentry.dev/self-hosted/"}]},{"chapter":"Operations tools","name":"Use Uptime Kuma on another provider for uptime checks","level":"Recommended","rule":"Check your public addresses from outside with Uptime Kuma, run at a different provider from the app.","check":"The monitor runs at a different provider from the app. Stopping the app in a test makes a page arrive within 5 minutes.","cost":"Open source. MIT licence, free to run. The small server at a second provider is paid.","url":"https://agentwranglers.se/playbook/stack/operations-tools/uptime-kuma","related":["/playbook/operations/running-the-service/liveness-and-readiness"],"sources":[{"label":"Uptime Kuma","url":"https://github.com/louislam/uptime-kuma"}]},{"chapter":"Operations tools","name":"Use PagerDuty for paging and keep schedules as code","level":"Recommended","rule":"Page people through PagerDuty, and keep its schedules, escalation policies and services in Terraform in your repository.","check":"`terraform plan` shows 0 changes against the live account. A test alert reaches the person on call by phone within 5 minutes.","cost":"Paid. PagerDuty charges per user. No open source pager passes the five tests, as How explains.","url":"https://agentwranglers.se/playbook/stack/operations-tools/pagerduty","related":["/playbook/operations/alerts-and-on-call/on-call-rota","/playbook/stack/operations-tools/ending-paging-tools"],"sources":[{"label":"GoAlert releases","url":"https://github.com/target/goalert/releases"},{"label":"PagerDuty REST API","url":"https://docs.pagerduty.com/developer/api/reference/rest/escalation-policies/list-escalation-policies"},{"label":"PagerDuty Terraform provider","url":"https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs"}]},{"chapter":"Operations tools","name":"Use pg_dump and pgBackRest for Postgres backups","level":"Recommended","rule":"Take a nightly `pg_dump` of every Postgres database, and add pgBackRest for point-in-time recovery once losing a day of data costs too much.","check":"The newest backup is younger than the recovery point objective. A restore into an empty server, run in the last 3 months, finished faster than the recovery time objective.","cost":"Open source. Both tools are free. The storage that holds the copies is paid.","url":"https://agentwranglers.se/playbook/stack/operations-tools/postgres-backups","related":["/playbook/operations/disaster-recovery/recovery-targets","/playbook/operations/disaster-recovery/off-site-backups"],"sources":[{"label":"pg_dump","url":"https://www.postgresql.org/docs/current/app-pgdump.html"},{"label":"PostgreSQL versioning policy","url":"https://www.postgresql.org/support/versioning/"},{"label":"pgBackRest","url":"https://pgbackrest.org/"}]},{"chapter":"Operations tools","name":"Avoid paging tools that are shutting down","level":"Avoid","rule":"Do not start on Opsgenie or the open source Grafana OnCall, and move off them before they stop.","url":"https://agentwranglers.se/playbook/stack/operations-tools/ending-paging-tools","related":["/playbook/stack/operations-tools/pagerduty"],"sources":[{"label":"Opsgenie end of sale and support","url":"https://www.atlassian.com/licensing/opsgenie"},{"label":"Grafana OnCall repository","url":"https://github.com/grafana-cold-storage/oncall"}]},{"chapter":"Product services","name":"Use Stripe for payments","level":"Recommended","applies":["payments"],"rule":"Take card payments and subscriptions through Stripe's hosted checkout and official SDK, and pin the SDK's major version.","check":"The lock file pins one Stripe SDK major version, and the webhook endpoint uses the API version that SDK pins. Every payment form runs on a Stripe-hosted page or embedded field.","cost":"Paid. Stripe charges a fee per transaction. No open source service can move card payments, because that needs a licensed payment provider.","url":"https://agentwranglers.se/playbook/stack/product-services/stripe-payments","related":["/playbook/payments/payment-events-and-reconciliation/provider-api-version","/playbook/payments/checkout-and-card-data/pci-saq-a-integration"],"sources":[{"label":"Stripe API versioning","url":"https://docs.stripe.com/api/versioning"},{"label":"Request a payment data export","url":"https://docs.stripe.com/get-started/data-migrations/pan-export"}]},{"chapter":"Product services","name":"Use Amazon SES to send transactional email","level":"Recommended","applies":["email"],"rule":"Send transactional email through Amazon SES with DKIM on your own domain, and route every bounce and complaint back to your app.","check":"Every send names a configuration set with an event destination. A test send to the SES mailbox simulator's bounce address puts that address on your own suppression list within 5 minutes.","cost":"Paid. Amazon charges per message sent. Open source mail servers send fine, but inbox providers trust senders by their history, so a new server's mail often lands in spam.","url":"https://agentwranglers.se/playbook/stack/product-services/ses-transactional-email","related":["/playbook/email/sending-domain-and-authentication/dkim","/playbook/email/unsubscribe-and-bounces/handle-bounces"],"sources":[{"label":"Email deliverability in Amazon SES","url":"https://docs.aws.amazon.com/ses/latest/dg/send-email-concepts-deliverability.html"}]},{"chapter":"Product services","name":"Use SimpleWebAuthn or py_webauthn for passkeys","level":"Recommended","applies":["accounts"],"rule":"Build passkey sign-in on SimpleWebAuthn in JavaScript or py_webauthn in Python, and never parse WebAuthn responses yourself.","check":"Every registration and sign-in passes through the library's verify function with the expected challenge, origin and relying party ID. A test with a wrong origin fails.","cost":"Open source. MIT and BSD-3-Clause licences, free to use in your own code.","url":"https://agentwranglers.se/playbook/stack/product-services/passkey-library","related":["/playbook/accounts/sign-in-methods/passkeys-first","/playbook/accounts/sign-in-methods/passkey-verification"],"sources":[{"label":"SimpleWebAuthn server package","url":"https://simplewebauthn.dev/docs/packages/server"},{"label":"Web Authentication Level 3","url":"https://www.w3.org/TR/webauthn-3/"}]},{"chapter":"Product services","name":"Use Keycloak when you need a sign-in server","level":"Recommended","applies":["accounts"],"rule":"When two or more apps share sign-in, or you need single sign-on or SAML, run Keycloak and connect apps through standard OpenID Connect.","check":"Each app reads its settings from the realm's discovery document, and no app imports a Keycloak-only client. A `kc.sh export` run in the last 12 months restored into a test server.","cost":"Open source. Apache-2.0, free to run on your own servers. Hosted identity services are proprietary and paid above a free tier.","url":"https://agentwranglers.se/playbook/stack/product-services/keycloak-self-hosted","related":["/playbook/accounts/sign-in-methods/auth-provider-or-library"],"sources":[{"label":"Keycloak import and export","url":"https://www.keycloak.org/server/importExport"},{"label":"OpenID Connect Discovery 1.0","url":"https://openid.net/specs/openid-connect-discovery-1_0.html"}]},{"chapter":"Product services","name":"Use the Web Push standard for browser push","level":"Recommended","rule":"Send push messages to browsers through the Web Push standard with VAPID keys, using an open source library on the server.","check":"Every browser send signs with your own VAPID key pair. A send that gets status 404 or 410 deletes that subscription in a test.","cost":"Open source. The standard is free and the libraries are open source. Each browser's push service delivers the message at no charge.","url":"https://agentwranglers.se/playbook/stack/product-services/web-push-standard","related":["/playbook/notifications/push-notifications/web-push-vapid","/playbook/notifications/push-notifications/prune-push-subscriptions","/playbook/stack/product-services/fcm-push"],"sources":[{"label":"RFC 8030 Generic Event Delivery Using HTTP Push","url":"https://www.rfc-editor.org/rfc/rfc8030"},{"label":"RFC 8292 VAPID for Web Push","url":"https://www.rfc-editor.org/rfc/rfc8292"}]},{"chapter":"Product services","name":"Use Firebase Cloud Messaging for Android and Apple app push","level":"Recommended","rule":"Send push messages to installed Android and Apple apps through Firebase Cloud Messaging with the Firebase Admin SDK.","check":"Every send uses the Admin SDK or the FCM v1 endpoint, and the code has no legacy FCM server key. A send to an unregistered token deletes that token in a test.","cost":"Free service. Google charges nothing to send, but the service is not open source. No open source service can reach these phones, because Android delivers through Google's servers and Apple only through APNs.","url":"https://agentwranglers.se/playbook/stack/product-services/fcm-push","related":["/playbook/notifications/push-notifications/apns-fcm-current-apis","/playbook/notifications/push-notifications/prune-push-subscriptions","/playbook/stack/product-services/web-push-standard"],"sources":[{"label":"Firebase Cloud Messaging","url":"https://firebase.google.com/docs/cloud-messaging"},{"label":"RFC 8030 Generic Event Delivery Using HTTP Push","url":"https://www.rfc-editor.org/rfc/rfc8030"}]},{"chapter":"Product services","name":"Use Twilio to send text messages","level":"Recommended","rule":"Send text messages through a Twilio Messaging Service with the official SDK, and keep phone numbers in E.164 form.","check":"Every send names a Messaging Service, not a single sender number. Every stored phone number matches `^\\+[1-9]\\d{1,14}$`.","cost":"Paid. Twilio charges per message and per phone number. No open source service can deliver text messages, because that needs contracts with phone carriers in each country.","url":"https://agentwranglers.se/playbook/stack/product-services/twilio-sms","related":["/playbook/notifications/text-messages/sms-sender-registration","/playbook/notifications/text-messages/sms-pumping"],"sources":[{"label":"Twilio Message resource","url":"https://www.twilio.com/docs/messaging/api/message-resource"}]},{"chapter":"Product services","name":"Use Plausible for analytics without cookies","level":"Optional","rule":"Count visits and events with a self-hosted Plausible Community Edition, which sets no cookies and stores no raw IP addresses.","check":"The site sets 0 analytics cookies in a fresh browser profile. A CSV export from the last 12 months opens with every page and event name.","cost":"Open source. The Community Edition is free to run on your own servers. The hosted service is paid.","url":"https://agentwranglers.se/playbook/stack/product-services/plausible-analytics","related":["/playbook/website/privacy/analytics-privacy"],"sources":[{"label":"Plausible data policy","url":"https://plausible.io/data-policy"},{"label":"Plausible Events API","url":"https://plausible.io/docs/events-api"}]},{"chapter":"Product services","name":"Avoid handling card numbers yourself","level":"Avoid","applies":["payments"],"rule":"Never let a card number, expiry date or security code reach your own servers, logs or database.","url":"https://agentwranglers.se/playbook/stack/product-services/no-own-card-handling","related":["/playbook/payments/checkout-and-card-data/pci-saq-a-integration"],"sources":[]},{"chapter":"Product services","name":"Avoid writing your own password and token code","level":"Avoid","applies":["accounts"],"rule":"Never write your own password hashing, session tokens, WebAuthn parsing or OpenID Connect handling.","url":"https://agentwranglers.se/playbook/stack/product-services/no-own-auth-code","related":["/playbook/accounts/sign-in-methods/auth-provider-or-library","/playbook/adversarial-testing/access-and-accounts/password-storage"],"sources":[]},{"chapter":"Product services","name":"Use Better Auth or django-allauth for sign-in inside the app","level":"Recommended","applies":["accounts"],"rule":"When one app runs its own sign-in, use Better Auth in TypeScript or django-allauth in Django, with users in your own database.","check":"Every sign-in, sign-up and sign-out route comes from the library. The app code imports no password hashing package of its own. A script exports every user, with the password hash, to JSON.","cost":"Open source. Both are MIT licensed and free to use in your own code.","url":"https://agentwranglers.se/playbook/stack/product-services/sign-in-library","related":["/playbook/accounts/sign-in-methods/auth-provider-or-library"],"sources":[{"label":"Better Auth","url":"https://www.better-auth.com/docs/introduction"},{"label":"Auth.js is now part of Better Auth","url":"https://better-auth.com/blog/authjs-joins-better-auth"},{"label":"django-allauth","url":"https://docs.allauth.org/en/latest/"}]},{"chapter":"Product services","name":"Use MJML to build email layouts","level":"Recommended","applies":["email"],"rule":"Write each email layout in MJML, compile it to HTML in the build with strict validation, and fill in the values with your template engine.","check":"The build compiles every `.mjml` file with `validationLevel: 'strict'` and fails on any error. No sent template is hand-written HTML.","cost":"Open source. MIT licence, free to use in any project.","url":"https://agentwranglers.se/playbook/stack/product-services/mjml-email","related":["/playbook/email/layout-and-accessibility/layout-tables"],"sources":[{"label":"MJML documentation","url":"https://documentation.mjml.io/"}]},{"chapter":"Product services","name":"Use Playwright or WeasyPrint to turn HTML into PDF","level":"Recommended","rule":"Build invoices, receipts and reports as HTML and print them to tagged PDF with Playwright in Node.js or WeasyPrint in Python.","check":"A test prints a sample invoice to PDF, and a PDF text extractor finds the order number as text. Every PDF call sets `tagged: true` or a `pdf/ua` variant.","cost":"Open source. Playwright is Apache-2.0 and WeasyPrint is BSD-3-Clause. Both are free to use in any project.","url":"https://agentwranglers.se/playbook/stack/product-services/html-to-pdf","related":["/playbook/stack/testing-and-code-quality/playwright","/playbook/adversarial-testing/injection/template-injection","/playbook/payments/invoices-and-records/e-invoicing"],"sources":[{"label":"Playwright page.pdf","url":"https://playwright.dev/docs/api/class-page#page-pdf"},{"label":"WeasyPrint","url":"https://doc.courtbouillon.org/weasyprint/stable/"}]},{"chapter":"AI software","name":"Use an open-weight model on vLLM or Ollama when it passes your evals","level":"Recommended","applies":["ai-features"],"rule":"Run your eval set on an open-weight model served by an open source server, such as vLLM or Ollama. Use it whenever it reaches your pass threshold.","check":"A decision record holds the eval pass rate of at least one open-weight model. Every paid model API in the project has a record showing it beat that rate.","cost":"Open source. vLLM is Apache-2.0 and Ollama is MIT, both free to run on your own servers. Model weights carry their own licences, and the hardware is yours to pay for.","url":"https://agentwranglers.se/playbook/stack/ai-software/open-weight-models","related":["/playbook/ai-features/deciding-to-use-a-model/smallest-passing-model"],"sources":[]},{"chapter":"AI software","name":"Use each provider's official SDK to call models","level":"Recommended","applies":["ai-features"],"rule":"Call each model provider through its own official SDK, inside one module of your own. Add a shared abstraction only when you call two or more providers.","check":"A script lists every model package in the dependency files. Each one is the official SDK of a provider the project calls, and only files inside the model module import it.","cost":"Paid. The SDKs are open source, but each provider charges for every call to its model API. Use one only when no open-weight model passes your evals.","url":"https://agentwranglers.se/playbook/stack/ai-software/official-model-sdk","related":["/playbook/ai-features/deciding-to-use-a-model/model-gateway"],"sources":[]},{"chapter":"AI software","name":"Use Zod or Pydantic to define structured output","level":"Recommended","applies":["ai-features"],"rule":"Define each structured answer once, as a Zod schema in TypeScript or a Pydantic model in Python. Send its JSON Schema to the model and validate the reply with the same definition.","check":"Every model call whose answer code reads passes a schema. A unit test feeds the validator one valid reply and one invalid reply, and only the valid one passes.","cost":"Open source. Both are MIT licensed and free to use in any app.","url":"https://agentwranglers.se/playbook/stack/ai-software/zod-or-pydantic","related":["/playbook/ai-features/structured-output-and-tools/schema-output"],"sources":[{"label":"JSON Schema draft 2020-12","url":"https://json-schema.org/draft/2020-12"}]},{"chapter":"AI software","name":"Use pgvector in PostgreSQL before a separate vector database","level":"Recommended","applies":["ai-features"],"rule":"Store embeddings in a pgvector column next to the rows they describe. Add a separate vector database only after a load test shows a limit PostgreSQL cannot meet.","check":"A load test with your expected vector count and query rate meets your latency target on PostgreSQL with an HNSW index. A decision record holds those numbers before any separate vector database is added.","cost":"Open source. PostgreSQL licence, free to run on your own servers. Managed PostgreSQL hosting is paid.","url":"https://agentwranglers.se/playbook/stack/ai-software/pgvector-first","related":["/playbook/ai-features/retrieval/hybrid-search"],"sources":[]},{"chapter":"AI software","name":"Use the Model Context Protocol to give agents your tools","level":"Recommended","applies":["public-api"],"rule":"Offer tools and data to outside AI agents as a Model Context Protocol server, built on an official MCP SDK.","check":"A test starts the server with the SDK's own client, lists the tools and calls each one once. Every call returns a result that matches the tool's output schema.","cost":"Open source. The protocol is open and the official SDKs are MIT or Apache-2.0 licensed, free to use.","url":"https://agentwranglers.se/playbook/stack/ai-software/mcp-for-tools","related":["/playbook/website/ready-for-ai-agents/mcp-and-tool-discovery","/playbook/ai-features/structured-output-and-tools/tool-design"],"sources":[{"label":"MCP specification","url":"https://modelcontextprotocol.io/specification/latest"},{"label":"MCP SDKs","url":"https://modelcontextprotocol.io/docs/sdk"}]},{"chapter":"AI software","name":"Use your test runner to run evals on a fixed example set","level":"Recommended","applies":["ai-features"],"rule":"Run AI evals as ordinary tests in the runner the project already uses, over a fixed set of examples kept in the repository.","check":"One command runs the eval set in CI. It exits non-zero when the pass rate falls below the threshold written in the test, for example 95 of 100.","cost":"Open source. Jest and pytest are both MIT licensed and free to run anywhere.","url":"https://agentwranglers.se/playbook/stack/ai-software/evals-in-test-runner","related":["/playbook/stack/testing-and-code-quality/jest","/playbook/ai-features/evals-and-quality/code-graders-first","/playbook/ai-features/evals-and-quality/evals-from-production"],"sources":[]},{"chapter":"AI software","name":"Use Promptfoo only when it passes your own tests","level":"Optional","applies":["ai-features"],"rule":"Add an open source eval tool, such as Promptfoo, only when it runs your example set in CI and fails the build on a drop.","check":"The tool reads the same example file as the test runner and exits non-zero in CI on a failed assertion. Removing the tool leaves the test-runner evals passing.","cost":"Open source. MIT licence, free to run in your own CI.","url":"https://agentwranglers.se/playbook/stack/ai-software/eval-tool-optional","related":["/playbook/testing/non-functional-tests/llm-evals"],"sources":[]},{"chapter":"AI software","name":"Use the coding agent that passes most of your own task set","level":"Recommended","rule":"Pick a coding agent by running your own fixed task set on each candidate. Keep its instructions in one AGENTS.md file that any agent can read.","check":"A decision record lists each candidate agent with its pass count and cost on the same task set. The chosen agent passed the most tasks, or the record says why not.","cost":"Open source. Aider and Cline are Apache-2.0 agents, free to run. Agents sold by model vendors are Paid, and so is any model API an agent calls.","url":"https://agentwranglers.se/playbook/stack/ai-software/coding-agent-by-tests","related":["/playbook/ai-assisted-development/measuring-and-improving/evaluate-agent-setup","/playbook/ai-assisted-development/setting-up-the-project/instructions-file"],"sources":[]}]}