awkno 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. awkno/__init__.py +25 -0
  2. awkno/_doctor.py +114 -0
  3. awkno/cli.py +497 -0
  4. awkno/corpus.py +216 -0
  5. awkno/generate.py +444 -0
  6. awkno/pages/agent-senses.json +13 -0
  7. awkno/pages/agent-vm.json +14 -0
  8. awkno/pages/aitherconnect.json +11 -0
  9. awkno/pages/aitherkvcache.json +11 -0
  10. awkno/pages/aitherzero.json +11 -0
  11. awkno/pages/awarena.json +14 -0
  12. awkno/pages/awask.json +15 -0
  13. awkno/pages/awbac.json +11 -0
  14. awkno/pages/awbrowse.json +13 -0
  15. awkno/pages/awdit.json +12 -0
  16. awkno/pages/awdk.json +16 -0
  17. awkno/pages/awevolve.json +15 -0
  18. awkno/pages/awfind.json +13 -0
  19. awkno/pages/awgit.json +12 -0
  20. awkno/pages/awgraph.json +12 -0
  21. awkno/pages/awiam.json +12 -0
  22. awkno/pages/awkit.json +13 -0
  23. awkno/pages/awkno.json +13 -0
  24. awkno/pages/awknowledge.json +12 -0
  25. awkno/pages/awm.json +13 -0
  26. awkno/pages/awmail.json +18 -0
  27. awkno/pages/awnboard.json +20 -0
  28. awkno/pages/awnest.json +19 -0
  29. awkno/pages/awnet.json +13 -0
  30. awkno/pages/awnix.json +20 -0
  31. awkno/pages/awnode.json +12 -0
  32. awkno/pages/awpack.json +13 -0
  33. awkno/pages/awpredict.json +11 -0
  34. awkno/pages/awprism.json +14 -0
  35. awkno/pages/awreason.json +14 -0
  36. awkno/pages/awrecover.json +13 -0
  37. awkno/pages/awrecurse.json +13 -0
  38. awkno/pages/awrelay.json +12 -0
  39. awkno/pages/awrepl.json +13 -0
  40. awkno/pages/awresearch.json +14 -0
  41. awkno/pages/awrun.json +13 -0
  42. awkno/pages/awseal.json +12 -0
  43. awkno/pages/awsh.json +13 -0
  44. awkno/pages/awshare.json +12 -0
  45. awkno/pages/awskills.json +12 -0
  46. awkno/pages/awsync.json +16 -0
  47. awkno/pages/awtunnel.json +12 -0
  48. awkno/pages/cited-research.json +14 -0
  49. awkno/pages/gobbonet-agentic.json +14 -0
  50. awkno/pages/guide-00.json +16 -0
  51. awkno/pages/guide-01.json +14 -0
  52. awkno/pages/guide-02.json +14 -0
  53. awkno/pages/guide-03.json +14 -0
  54. awkno/pages/guide-04.json +15 -0
  55. awkno/pages/guide-05.json +16 -0
  56. awkno/pages/guide-06.json +15 -0
  57. awkno/pages/guide-07.json +15 -0
  58. awkno/pages/guide-08.json +17 -0
  59. awkno/pages/guide-09.json +13 -0
  60. awkno/pages/guide.json +22 -0
  61. awkno/pages/law-01.json +11 -0
  62. awkno/pages/law-02.json +11 -0
  63. awkno/pages/law-03.json +11 -0
  64. awkno/pages/law-04.json +11 -0
  65. awkno/pages/law-05.json +11 -0
  66. awkno/pages/law-06.json +11 -0
  67. awkno/pages/law-07.json +11 -0
  68. awkno/pages/law-08.json +11 -0
  69. awkno/pages/law-09.json +11 -0
  70. awkno/pages/law-10.json +11 -0
  71. awkno/pages/law-11.json +11 -0
  72. awkno/pages/law-12.json +11 -0
  73. awkno/pages/law-13.json +11 -0
  74. awkno/pages/law-14.json +11 -0
  75. awkno/pages/law-15.json +11 -0
  76. awkno/pages/law-16.json +11 -0
  77. awkno/pages/law-17.json +11 -0
  78. awkno/pages/law-18.json +11 -0
  79. awkno/pages/law-19.json +11 -0
  80. awkno/pages/one-surface.json +15 -0
  81. awkno/pages/provenance.json +14 -0
  82. awkno/pages/shared-worktree.json +14 -0
  83. awkno/pages/the-front-door.json +15 -0
  84. awkno/pages/the-reasoning-loop.json +14 -0
  85. awkno/pages/who-what-did.json +13 -0
  86. awkno-0.2.0.dist-info/METADATA +321 -0
  87. awkno-0.2.0.dist-info/RECORD +91 -0
  88. awkno-0.2.0.dist-info/WHEEL +5 -0
  89. awkno-0.2.0.dist-info/entry_points.txt +2 -0
  90. awkno-0.2.0.dist-info/licenses/LICENSE +118 -0
  91. awkno-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "adopt": "Run one GobboNet campaign where the harness, not the human, keeps the notes.",
3
+ "category": "brick",
4
+ "description": "Kind: tool\n\nProblem\nLong roleplay campaigns decay: the model loops, forgets what each character knows, and the only fix is a human curating notes by hand. Per-NPC knowledge scoping is exactly the memory-boundary problem agents already have.\n\nInstall\npip install awdk",
5
+ "see_also": [
6
+ "awdk",
7
+ "awm",
8
+ "awgraph",
9
+ "awknowledge"
10
+ ],
11
+ "status": "planned",
12
+ "synopsis": "GobboNet campaigns with a real agent brain",
13
+ "topic": "gobbonet-agentic"
14
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### Three words you will see everywhere\n\n**Aitherium** is the company and the platform: the hosted service at aitherium.com,\nthe accounts, the marketplace, the people.\n\n**AitherOS** is the operating system underneath it — not a Linux you install, but the\nrunning fleet of services that gives agents memory, identity, tools and a place to\ntalk. You never have to run AitherOS yourself. Everything in this guide works on your\nown laptop; the platform is there when you want more.\n\n**aw** is short for **Aither World**. Every tool in the family is named as a phrase:\n`awdk` is *Aither World Dev Kit*, `awnix` is *Aither World Nix* (a Linux), `awsh` is\n*Aither World Shell*. If a name does not read aloud as \"Aither World <something>\", it\nis not one of ours. We call each of these tools a **brick** — small, standalone, and\nuseful on its own before it is useful together.\n\n### What an agent actually is\n\nA chatbot answers. An **agent** *acts*: it can run a command, read a file, search the\nweb, send a message, and then decide what to do next based on what happened. The\ndifference is tools. Give a brain tools and a job, and you have an agent.\n\nSo an agent is three things:\n\n- a **brain** — the model that reads and writes language;\n- **tools** — functions the brain is allowed to call;\n- a **job** — what it is for, written down in a small file.\n\nChapter 4 has you write that file. It is shorter than this page.\n\n### The brains are open\n\nAitherium's own brains are open models you can download and run:\n\n- **aither-orchestrator** — an 8-billion-parameter model the platform itself runs on.\n It fits on an ordinary laptop.\n- **Bonsai** — a family of models squeezed to about *one bit per weight*. Bonsai-27B is\n a 27-billion-parameter brain in 3.8 GB, and it runs on a plain CPU with 8 GB of RAM.\n\n\"Open\" means the numbers that make up the model are published. You can run them\nwith no account, no network, no bill. Chapter 2 does exactly that.\n\n### How this guide works\n\nEvery chapter from here on has two columns. **Teach** (left) explains what a thing\nis and why it exists, in plain words. **Do** (right) is the exact command to type,\nwhat you should see, and what to do if you do not see it. Every chapter ends with\none command that proves you finished.\n\nYou never need a later chapter to do an earlier one. If you stop after chapter 3 you\nhave a working local brain you can talk to, and that is a fine place to stop.\n\nWHAT YOU LEARNED\n\n- An agent is a program that can take actions, not just answer.\n- A brain is the model behind it; Bonsai and aither-orchestrator are ours and they are open.\n- \"aw\" means Aither World - every brick name is a phrase: awdk is Aither World Dev Kit.",
4
+ "category": "guide",
5
+ "description": "Chapter 0 of 9 -- What Aitherium, AitherOS and the aw* bricks are - in plain words, before you install anything.\n\nAbout 5 minutes. Read online: https://aitherium.github.io/awknowledge/path/00-welcome.html",
6
+ "see_also": [
7
+ "awknowledge",
8
+ "awdk",
9
+ "awnix",
10
+ "guide-01"
11
+ ],
12
+ "slug": "00-welcome",
13
+ "status": "published",
14
+ "synopsis": "Welcome to Aither World",
15
+ "topic": "guide-00"
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### What you are installing\n\n**awdk** — *Aither World Dev Kit* — is one program with many verbs. Everything in\nthis guide runs through it: starting a brain (`adk quickstart-local`), chatting\n(`adk chat`), building an agent (`adk init`), installing packs (`adk install`). One\ninstall, and the rest of the chapters are verbs of the same command.\n\nThe command is called `adk`. Short for *agent dev kit*; it is the same thing.\n\n### What \"pip\" is\n\nMost of the kit is written in Python, and `pip` is Python's installer — the way\n`apt` installs on Ubuntu or the App Store installs on a phone. `pip install awdk`\ndownloads the kit from PyPI (the public Python package index) and puts `adk` on your\nPATH, which is the list of folders your terminal searches when you type a command.\n\nThat last part matters: a terminal reads PATH when it *opens*. If `adk` is \"not\nfound\" right after installing, the window you are in is simply older than the\ninstall. Open a new one.\n\n### What the wizard does\n\n`adk wizard` asks a handful of questions and writes one small config folder in your\nhome directory, `~/.aither/`. Pressing Enter accepts every default, and the defaults\nare right for this guide. It changes nothing else on your machine.\n\n### What the doctor does\n\n`adk doctor` is a list of checks, one per line, each ending in **OK**, **WARN** or\n**FAIL**. It does not guess; it tries the thing and reports. Any time something is\nodd later in the guide, this is the first command to run. A **WARN** about not being\nlogged in is expected — we do that in chapter 3, and only if you want to.\n\nDO\n\n1. $ pip install awdk\n you should see: Successfully installed awdk\n if not: If `pip` is not found, install Python from python.org first (tick 'Add to PATH'), then open a NEW terminal and try again - or use one of the two one-liners below, which install Python for you.\n2. (optional) $ curl -fsSL https://aitherium.com/install.sh | sh\n you should see: macOS / Linux only - the installer walks through Python, the kit and a first check\n if not: Skip this if `pip install awdk` already worked; it is the same kit.\n3. (optional) $ irm https://aitherium.com/install.ps1 | iex\n you should see: Windows only (PowerShell) - the same installer\n if not: Skip this if `pip install awdk` already worked.\n4. $ adk wizard\n you should see: a short questionnaire; answer with Enter to accept the defaults\n if not: If `adk` is not found, close the terminal and open a new one - the install changed your PATH and the old window does not know.\n5. $ adk doctor\n you should see: every line ends in OK (a WARN about a cloud login is fine - we have not logged in yet)\n if not: Read the first line that is not OK; it names the fix. Nothing below it matters until that one is green.\n\ndone when: $ adk doctor\n you should see: no FAIL lines\n\nWHAT YOU LEARNED\n\n- pip installs Python programs; awdk is one.\n- The wizard writes a small config in your home folder (~/.aither/). Nothing else changes.\n- `adk doctor` is the question you ask whenever something is odd: it checks, it does not guess.",
4
+ "category": "guide",
5
+ "description": "Chapter 1 of 9 -- One command puts the whole kit on your machine. Then you check it worked.\n\nAbout 10 minutes. Read online: https://aitherium.github.io/awknowledge/path/01-install-awdk.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "guide-02"
9
+ ],
10
+ "slug": "01-install-awdk",
11
+ "status": "published",
12
+ "synopsis": "Install awdk",
13
+ "topic": "guide-01"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### What a model is\n\nA language model is a very large file of numbers — billions of them — and a\nprogram that uses those numbers to turn text in into text out. That is the whole\nthing. When people say \"the AI\", they mean the numbers plus the program that runs\nthem. Running it is called **serving**: the program loads the file into memory and\nwaits on a port (a numbered door on your machine) for questions.\n\nThink of a vinyl record and a turntable. The record is the model; the turntable is\nthe server; the music only exists while both are together. The record is useless on\nits own, and the turntable plays whatever you put on it.\n\n### What \"8B\" means\n\n\"8B\" is eight billion numbers. Each number normally takes 16 bits, so an 8B model is\nabout 16 GB — too big for most laptops' memory. **Quantization** stores each number\nin fewer bits. At 4 bits the same model is ~4.5 GB and almost as good. At **1 bit** —\nwhich is what Bonsai does — a 27-billion-number model fits in 3.8 GB and runs on a\nplain CPU with 8 GB of RAM.\n\nFewer bits is a trade: smaller and faster, very slightly less precise. The kit picks\nthe trade that fits your machine, so you do not have to.\n\n### The open model stack\n\nTwo families of brains are Aitherium's own, and both are open:\n\n- **aither-orchestrator** — the 8-billion-parameter brain the platform itself runs\n on. This is the one `adk quickstart-local` downloads for you (an openly published\n GGUF file, quantized to fit your RAM).\n- **Bonsai** — the 1-bit family from PrismML. Bonsai-27B is the \"big brain in a tiny\n box\" option, and because 1-bit files need a special version of the server, the kit\n ships it as a container (`adk bonsai-local`, optional below).\n\nOpen means: the file is public, you can download it, and nothing about it phones\nhome. Unplug your network after this chapter — it still answers.\n\n### What `quickstart-local` does, step by step\n\nIt prints five steps as it goes:\n\n1. **Detects your hardware** — CPU or GPU, how much memory.\n2. **Picks a backend** — the program that will serve the model: plain `llama.cpp`\n on a CPU (no dependencies), Ollama if you already have it, vLLM on an NVIDIA card\n with Docker.\n3. **Installs it** and downloads a model sized for you. This is the slow step.\n4. **Verifies** it by asking the model a question and checking an answer comes back.\n A failed check fails the command — \"quickstart\" means *proven working*.\n5. **Registers** the brain with the kit, so every later chapter can find it.\n\nDO\n\n1. $ adk quickstart-local\n you should see: five numbered steps, [1/5] Detecting hardware... through [5/5], then `Config saved:` and an endpoint such as http://localhost:8200/v1\n if not: Each step prints what it is doing. If step 3 fails to download, check your disk has 6 GB free and run the same command again - the download resumes where it stopped.\n2. (optional) $ adk bonsai-local\n you should see: Starting Bonsai-27B ... and a healthy endpoint on port 8090. If the container image is not on your machine yet, it BUILDS it first from PrismML's public sources - a 3.8 GB download plus a compile, up to ~40 minutes once, seconds ever after.\n if not: Needs Docker Desktop. Without it, skip - the brain from the previous step is already running; Bonsai is explained on the left and you can come back for it.\n\ndone when: $ adk backend status\n you should see: Backend: llamacpp (or ollama / vllm) and Endpoint: http://localhost:... - the brain the kit will use from now on\n\nWHAT YOU LEARNED\n\n- A model is a file of numbers; the server loads it and answers requests on a port.\n- \"8B\" is eight billion of those numbers; quantization stores each in fewer bits so it fits in less memory.\n- The brain is now on YOUR machine. Unplug the network - it still answers.",
4
+ "category": "guide",
5
+ "description": "Chapter 2 of 9 -- Run an open model on your own computer, offline. Learn what \"8B\" and \"1-bit\" actually mean.\n\nAbout 20 minutes. Read online: https://aitherium.github.io/awknowledge/path/02-first-brain.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "guide-03"
9
+ ],
10
+ "slug": "02-first-brain",
11
+ "status": "published",
12
+ "synopsis": "Your first local brain",
13
+ "topic": "guide-02"
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### A prompt is a request, not a command\n\nWhen you type to a brain you are not giving an instruction a computer obeys; you\nare handing over a request it interprets. Two things follow. First, *how* you ask\nchanges what comes back — a vague question gets a vague answer, and \"explain it\nlike I have never seen a terminal\" genuinely works. Second, the brain knows only\nwhat it was given: the model file and your message. It cannot see your files,\nyour calendar, or the time unless something hands those to it. Chapter 4 is\nabout handing things to it — that is what tools are.\n\n### Two ways to talk\n\n**One question.** `adk backend test` sends a single fixed prompt to whatever brain\nthe kit is pointed at and prints the reply with the provider, the model and a\ntoken count. It is the smoke detector: if this says `Status: OK`, everything\nabove it in the stack is fine.\n\n**A conversation.** `adk start` opens a chat. Run it inside a folder and it also\nreads that folder — Python files get indexed — so you can ask \"what does this\nproject do?\" and get an answer grounded in what is actually there. The banner at\nthe top tells you which brain it found. Type `/quit` to leave.\n\n### Local or cloud is per task, not forever\n\nEverything so far runs on your machine with no account. Aitherium also hosts\nbigger brains, and `adk login` connects you to them. It is optional in this\nguide, and it stays optional: every later chapter works offline.\n\nIf you do log in, notice what does *not* happen: you never type a password into\nthe terminal. The kit prints a short code and a web address; you open the page,\ntype the code, and come back. That is a **device code** login — the same thing a\nTV does when you sign in to a streaming app — and it means the terminal never\nholds your password at all.\n\n`adk whoami` tells you which state you are in. Both answers are fine.\n\nDO\n\n1. $ adk backend test\n you should see: Provider: ..., a one-line Response: from your brain, and Status: OK\n if not: If it says FAILED, the brain from chapter 2 is not running. `adk quickstart-local` again restarts it; `adk backend status` shows what the kit expects to find.\n2. $ adk start\n you should see: a small banner - Workspace, Directory, LLM: your local brain and its address - then a `You >` prompt. Type a question; type /quit to leave.\n if not: If the LLM line names the cloud or says nothing was found, the brain is down - see the step above. Run `adk start` inside any folder; it reads that folder so it can answer questions about it.\n3. (optional) $ adk login\n you should see: a short code and a URL; open the URL, type the code, come back - Logged in as ...\n if not: Logging in is optional. It connects you to hosted brains and your workspace. Stay local if you prefer - everything in the next chapters works offline too.\n4. (optional) $ adk whoami\n you should see: your account, or Not logged in\n if not: Both answers are fine. This just tells you which you are.\n\ndone when: $ adk backend test\n you should see: Status: OK\n\nWHAT YOU LEARNED\n\n- A prompt is a request; the brain's answer depends on what it was given, which is why chapter 4 gives it tools.\n- Local vs cloud is a choice you make per task, not once: `adk backend status` shows which is current.\n- Your login is a device code you type into a web page, never a password typed into a terminal.",
4
+ "category": "guide",
5
+ "description": "Chapter 3 of 9 -- Ask your brain one question, then open a chat. Optionally connect to the cloud for the bigger brains.\n\nAbout 15 minutes. Read online: https://aitherium.github.io/awknowledge/path/03-talk-to-it.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "guide-04"
9
+ ],
10
+ "slug": "03-talk-to-it",
11
+ "status": "published",
12
+ "synopsis": "Talk to it",
13
+ "topic": "guide-03"
14
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### What makes an agent an agent\n\nA brain answers. An **agent** acts. The difference is tools: functions the brain\nis allowed to call, and a loop that lets it call one, look at the result, and\ndecide what to do next. Give a brain tools and a job, and it can check the time,\nread a file, search, send a message — and *then* answer.\n\nThink of a new employee. The brain is what they know; the tools are the badge,\nthe phone and the filing cabinet; the job is the one-page description you hand\nthem on day one. Chapter 3 was a conversation with someone who has no badge.\n\n### The three files\n\n`adk init` writes a folder with three files, and they are the whole agent:\n\n- **agent.py** — the agent itself: about twenty lines that create it, give it\n one tool, and ask it a question. Read it; it is shorter than this page.\n- **config.yaml** — its settings: a name (`identity`), which brain to use\n (`llm_backend: auto` means \"whatever chapter 2 set up\"), a port, and which\n packs to load.\n- **tools.py** — your own tools. Two examples are already there.\n\n### What a tool is\n\nA tool is an ordinary function with a decorator on top:\n\n```python\n@agent.tool\ndef hello(name: str) -> str:\n \"\"\"Greet someone by name.\"\"\"\n return f\"Hello, {name}!\"\n```\n\nThe decorator registers it with the brain, and the docstring is what the brain\nreads to decide *when* to use it — so write the docstring for the brain, not for\nyou. Nothing else changes: it is still a Python function you can call yourself.\n\n### Two ways to run it\n\n**Once.** `python agent.py` creates the agent, asks it the question written at\nthe bottom of the file, prints the reply, and exits. This is how you try a change.\n\n**As a service.** `adk run` keeps the agent listening on a port so other\nprograms can talk to it — the web, other agents, and the packs in chapter 5. You\ndo not need it yet; it is here so the word \"server\" is not a surprise later.\n\nDO\n\n1. $ adk init my-agent\n you should see: Created AitherADK project at my-agent/ with three files - agent.py (your agent), config.yaml (its settings), tools.py (its custom tools) - and Next steps\n if not: If it says the folder exists and is not empty, pick another name. init never overwrites.\n2. $ cd my-agent\n you should see: your prompt now shows the my-agent folder\n if not: The next commands must run inside the folder init created.\n3. $ python agent.py\n you should see: one greeting reply, printed by your agent - agent.py asks it to say hello, and the agent has a hello tool to do it with\n if not: If it cannot reach a brain, chapter 2's brain is down: `adk backend test` tells you. If `python` is not found, try `python3`.\n4. (optional) $ adk run\n you should see: Starting AitherADK server - identity: my-agent, port: 8080 - then it keeps running; press Ctrl+C to stop\n if not: If port 8080 is busy, `adk run --port 8081`. Running as a server is how other programs (and chapter 5's packs) talk to your agent; for now the direct run above is enough.\n\ndone when: $ python agent.py\n you should see: a reply from your agent\n\nWHAT YOU LEARNED\n\n- An agent is three files - the agent, its config, its tools - and config.yaml is the job description (which brain, which packs, what port).\n- A tool is an ordinary function with a decorator; the brain decides when to call it.\n- `python agent.py` runs it once; `adk run` keeps it serving so other things can reach it.",
4
+ "category": "guide",
5
+ "description": "Chapter 4 of 9 -- An agent is a brain plus tools plus a job. You will scaffold one in three files and watch it use a tool.\n\nAbout 30 minutes. Read online: https://aitherium.github.io/awknowledge/path/04-build-an-agent.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "awskills",
9
+ "guide-05"
10
+ ],
11
+ "slug": "04-build-an-agent",
12
+ "status": "published",
13
+ "synopsis": "Build your first agent",
14
+ "topic": "guide-04"
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### What a pack is\n\nA **pack** is an agent you did not write. Someone — maybe Aitherium, maybe another user — built a complete agent with brain settings, tools, and a job, and published it so you can have it in one line.\n\nYou already met packs: Bonsai in chapter 2 is a pack (it comes as a container), and the \"tools\" chapter 4 talks about are tools that packs bring with them.\n\n### The pack you will try\n\nOpenClaw is a research agent — it searches the web, gathers sources, cites them, and builds a knowledge graph so you can ask follow-up questions and it remembers context across them. Research agents without your own tools are useful, but the ones with tools are agents: they can *act* (search, retrieve, synthesize) instead of just guessing.\n\n### Where packs come from\n\nRight now, packs ship inside awdk. When you `pip install awdk`, you get a bundled catalog of packs — free ones like the Orchestrator, and paid tiers like Hydra (testing and QA). The packs live inside the kit.\n\nThe **awpack** public registry is being built. When it ships, this page will change to \"go to the marketplace, browse, pick one, install\". Until then, the bundled set is what you have.\n\n### The difference between packs and skills\n\nA **skill** is smaller — one procedure. Research a question, send an email, approve a decision. Skills live in **awskills** (https://aitherium.github.io/awskills/) and are used by agents to do specific jobs. A pack is a whole agent; a skill is one thing an agent can do. Think of a pack as a person and a skill as a technique that person knows.\n\nDO\n\n1. $ adk packs\n you should see: Available Agent Packs - a short table (analyst, claude-code, code-discipline, gobbonet, hermes, openclaw) and Install with: adk install pack:<name>\n if not: An empty table means the kit did not install completely. `pip install --force-reinstall awdk` restores the bundled packs.\n2. $ adk install pack:openclaw\n you should see: Installing openclaw... then Installed to: <a folder in your home directory> and Next steps\n if not: The name must match a row from `adk packs` exactly - `pack:openclaw`, lower case.\n3. $ adk run --agents openclaw\n you should see: Starting AitherADK fleet server - agents: openclaw, port: 8080 - the pack's agent is now serving; Ctrl+C stops it\n if not: If port 8080 is busy, add --port 8081. If it says the agent is unknown, the install step did not finish - read its last line.\n\ndone when: $ adk run --agents openclaw\n you should see: Starting AitherADK fleet server - agents: openclaw\n\nWHAT YOU LEARNED\n\n- A pack is an agent in a box - brain settings, tools, skills - that installs in one command.\n- Packs ship inside awdk today; the public pack registry (awpack) is being built, and this page says so until it is not.\n- Skills are smaller than packs - one procedure each - and live in awskills.",
4
+ "category": "guide",
5
+ "description": "Chapter 5 of 9 -- Someone already built the agent you want. Find it, install it, run it - and know what is still coming.\n\nAbout 15 minutes. Read online: https://aitherium.github.io/awknowledge/path/05-agent-packs.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "awskills",
9
+ "awpack",
10
+ "guide-06"
11
+ ],
12
+ "slug": "05-agent-packs",
13
+ "status": "published",
14
+ "synopsis": "Agent packs",
15
+ "topic": "guide-05"
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### How the kit picks a brain for your machine\n\nChapter 2 ran a brain on your CPU or GPU. But how did the kit know which one you had? And how big a model is safe for your RAM?\n\nThe kit **measures your hardware** — how much RAM, whether you have a GPU, whether Docker is running — and picks a model and a backend that fit. An 8B model needs at least 8 GB of RAM (or VRAM); Bonsai's 1-bit family fits in less. vLLM on NVIDIA is faster than llama.cpp on a CPU. The kit makes that choice so you do not have to know the numbers.\n\n### Model size and memory\n\nA **model** is numbers — weights. An \"8B model\" has 8 billion of them. A 4-bit quantization stores each in 4 bits instead of 32, so it fits in 8× less memory. This is why quantization exists: the same brain in a smaller space.\n\n- **aither-orchestrator** is the open 8-billion-parameter brain the platform itself runs on. It is a good default.\n- **Bonsai** is the 1-bit family — Bonsai-27B is 27 billion parameters in 3.8 GB. It fits on a machine with 8 GB of RAM and runs on a CPU. It is the brain for tiny machines.\n\n### What a backend is\n\nA **backend** is the software that loads and runs a brain. llama.cpp runs models on CPUs and small GPUs. vLLM runs them on NVIDIA with high throughput. Ollama is a middle ground — easy to use, a bit slower.\n\nRegistering a backend is what lets agents anywhere reach it. A brain nobody registered is a brain nobody can call. `adk backend status` tells you which brain the kit is pointed at. `adk up` registers this machine as a service.\n\nDO\n\n1. $ adk quickstart\n you should see: hardware detected, a recommended setup for it, and a running backend\n if not: On a machine with no GPU this is the same as chapter 2 and says so. On NVIDIA it offers vLLM if Docker is present.\n2. $ adk backend status\n you should see: Backend, Endpoint and Model - the brain the kit is currently pointed at\n if not: If it says not configured, chapter 2 did not finish - run `adk quickstart-local` first.\n3. (optional) $ adk up\n you should see: your agent deployed as a service on this box, enrolled with your workspace, a heartbeat every 60 s\n if not: Needs a login (chapter 3) and Docker. Without either, `adk run` from chapter 4 is the offline equivalent.\n\ndone when: $ adk backend status\n you should see: the backend you chose, with an Endpoint that answers\n\nWHAT YOU LEARNED\n\n- Model size must fit memory; the kit measures your RAM/VRAM and picks, it does not ask you to know.\n- aither-orchestrator is the open 8B brain the platform itself runs on; Bonsai is the 1-bit family for tiny machines.\n- Registering a backend is what lets agents anywhere use it - a brain nobody registered is a brain nobody can reach.",
4
+ "category": "guide",
5
+ "description": "Chapter 6 of 9 -- CPU or GPU, laptop or rack - how the open model stack picks a model for what you have, and how a brain gets registered.\n\nAbout 30 minutes. Read online: https://aitherium.github.io/awknowledge/path/06-your-own-hardware.html",
6
+ "see_also": [
7
+ "awdk",
8
+ "awnode",
9
+ "guide-07"
10
+ ],
11
+ "slug": "06-your-own-hardware",
12
+ "status": "published",
13
+ "synopsis": "Your own hardware",
14
+ "topic": "guide-06"
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### What \"immutable\" means\n\nAn immutable Linux is one where `/usr` — the folder containing all system software — cannot be changed at runtime. A traditional computer is like a notebook: you write in it, erase things, write over them. An immutable system is like a printed book: the words never change by hand, and the only way to make a new version is to print a new book and keep the old one.\n\nEvery time your agent runs code that changes the system — installs a package, updates a config, patches a binary — that change becomes a NEW IMAGE. You can compare the two, see exactly what changed, and if something went wrong you can `bootc rollback` back to the printed book you trusted.\n\n### Cloud-init makes key-only login actually usable\n\nAn awnix machine has NO PASSWORD. Access is by SSH key only. You might think that sounds impossible — what if you lose the key? The answer is **cloud-init**: a service that runs once on first boot and injects whatever SSH keys you tell it to. On a cloud image you launched, your launch keypair arrives automatically. On bare metal you place a key yourself. Either way, nobody ever types a password, which means there is nothing to leak.\n\nTwo things must be true together or the whole idea breaks:\n- **cloud-init** must run and inject the key\n- **NOPASSWD sudo** must be enabled, so the account can administer the box without a password (the same setup `ec2-user` and `ubuntu` use)\n\n### What bootc does\n\n**bootc** — the *bootable container* system — moves a container image from `podman run` to bare metal. You build an image on your laptop, push it to a registry, and a machine boots it directly. Every boot is atomic: the system either completes or rolls back, never a partial state. An update that goes wrong is `bootc rollback`, not an afternoon of recovery.\n\n### The aw tools are preinstalled\n\nawnix ships 19 of the `aw*` tools ready to import: awgit (leases), awgraph (call graphs), awrelay (messaging), awseal (signing), and 15 others. Your agent does not have to install them — they are already there. That is the point of a base image: the guarantees are built in, and every agent on top gets them for free.\n\n### Layering an agent on top\n\nAn agent is three things:\n- The awnix base (immutable, atomic updates, built-in tools)\n- awdk — the agent runtime\n- Your skills, packs and credentials\n\nA Dockerfile that layers on top looks like:\n\n```dockerfile\nFROM awnix:latest\nRUN pip3 install --no-cache-dir awdk\n#... your packs and credentials\n```\n\nThe base is read-only. Your agent runs as a service (using the unit template in the awnix repo), and when it needs a new version, you rebuild the image and `bootc upgrade`.\n\nDO\n\n1. $ git clone https://github.com/Aitherium/awnix\n you should see: a folder named awnix with a Containerfile inside\n if not: Install git (git-scm.com) and open a new terminal.\n2. $ cd awnix\n you should see: your prompt shows the awnix folder; the build below runs here\n if not: The Containerfile is in this folder; the build must run from inside it.\n3. $ podman build -t awnix:latest -f Containerfile .\n you should see: an image named awnix:latest, built from the awnix repo you cloned\n if not: Install podman (or Docker - `docker build` works the same). Clone https://github.com/Aitherium/awnix first and run this inside it.\n4. $ podman run --rm awnix:latest awgit --version\n you should see: a version - proof the aw* tools are preinstalled in the base\n if not: If the command is not found inside the image, the build used a different Containerfile. Rebuild from the repo root.\n5. (optional) $ bootc status\n you should see: on a machine BOOTED from the image: the running image and the rollback target\n if not: Only meaningful on a box that booted from awnix (the ISO or a cloud image). Inside a container it is absent, and that is expected.\n\ndone when: $ podman run --rm awnix:latest awgit --version\n you should see: a version string\n\nWHAT YOU LEARNED\n\n- Immutable means /usr cannot change at runtime; every change is a new image you can diff and roll back.\n- No password exists on the box; keys only. NOPASSWD sudo is what makes key-only login usable.\n- Your agent layers ON TOP (FROM awnix, pip install awdk, add your pack) - the guarantees stay in the base.",
4
+ "category": "guide",
5
+ "description": "Chapter 7 of 9 -- An immutable Linux built for machines where software writes software. Your agent becomes three lines in a Dockerfile.\n\nAbout 45 minutes. Read online: https://aitherium.github.io/awknowledge/path/07-deploy-on-awnix.html",
6
+ "see_also": [
7
+ "awnix",
8
+ "awdk",
9
+ "guide-08"
10
+ ],
11
+ "slug": "07-deploy-on-awnix",
12
+ "status": "published",
13
+ "synopsis": "Deploy on awnix",
14
+ "topic": "guide-07"
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### Two agents, one file\n\nWhen two agents edit the same code, they can overwrite each other's work. Git tracks which *lines* changed, but it doesn't know that agent A is editing a function and agent B is reindenting it — it just sees lines moving, and when both agents commit their changes, the merge becomes a puzzle Git cannot solve alone. A **lease** stops this: before you edit a file, you claim it for a time period. If someone else holds the lease, you know to stay out; if you crash, the lease expires and the file becomes editable again.\n\n### Looking without reading everything\n\nAn agent needs to know who calls a function, what it calls, and what tests cover it — the relationships that decide which files matter to a change. A **call graph** indexes the repository by parsing it into symbols, calls, and callers, so you can answer \"who calls main\" without reading every file that matches the name. The index is built once and cached on your machine, not downloaded.\n\n### Talking through a channel\n\nAn agent that finds a bug has nowhere to tell another agent except its own transcript, which nobody reads. A **relay** is a chat channel a human can read, where agents leave findings, alerts and coordination messages in a format humans can read and agents can parse. Because the channel is durable and searchable, a decision made there is not relitigated when the next agent hits the same symptom.\n\n### Learning without re-learning\n\nMemory lets an agent record something it learned — a recipe, a decision, a measurement — so the next agent doesn't have to re-derive it. **awm** stores it in scopes so an agent's learning stays in the right context: a project-level recipe is not written to the platform, and your personal preferences do not crowd out shared ones.\n\nDO\n\n1. $ pip install awgit awgraph awrelay awm\n you should see: four packages installed\n if not: Install them one at a time to see which fails; each is independent.\n2. $ awgit lease acquire README.md\n you should see: a lease on the file, with its holder and expiry\n if not: Run it inside a git repository. A lease is about a file in a repo.\n3. $ awgraph query \"who calls main\"\n you should see: a list of callers, or 'no index yet' with the command to build one\n if not: Build the index first with the command it prints; the graph is derived from your code, not downloaded.\n4. $ awrelay send \"#general\" \"hello from the guide\" --kind note\n you should see: message delivered, with a channel and an id\n if not: awrelay needs a relay endpoint (your own, or the hosted one after chapter 3's login). The error names which is missing.\n\ndone when: $ awgit lease list\n you should see: the lease you took, still held\n\nWHAT YOU LEARNED\n\n- A lease says \"I am editing this\"; it expires, so a crashed agent cannot hold a file forever.\n- A call graph answers \"who calls this\" without reading everything.\n- Messages between agents go through a channel a human can read too - that is how decisions stop being relitigated.\n- The laws (Reference > Laws) are WHY each of these exists: every one was a real failure first.",
4
+ "category": "guide",
5
+ "description": "Chapter 8 of 9 -- Two agents editing the same code without sweeping each other's work - leases, a call graph, messaging, memory.\n\nAbout 30 minutes. Read online: https://aitherium.github.io/awknowledge/path/08-many-agents.html",
6
+ "see_also": [
7
+ "awgit",
8
+ "awgraph",
9
+ "awrelay",
10
+ "awm",
11
+ "guide-09"
12
+ ],
13
+ "slug": "08-many-agents",
14
+ "status": "published",
15
+ "synopsis": "Many agents, one repo",
16
+ "topic": "guide-08"
17
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "### The shell's one answer\n\nA shell has exactly one response to a line it doesn't recognize: `command not found`. So every time you want to look something up — what is the hash of this commit, what does this API need, how does time work on this machine — you leave the terminal and reach for a browser, losing the directory, the environment and the session you were in. The omnibox makes that line reach an agent instead, so you stay.\n\n### It is a hook, not a new shell\n\nThis is not a new shell language. It is a **hook** that installs into your existing shell — PowerShell, bash or zsh — so your terminal stays your terminal. Your directory, your environment variables, your history, the session you are in: they are all still there. The omnibox just intercepts the moment before `command not found` and asks an agent instead.\n\n### Scripts stay silent\n\nEvery script tests whether a tool is installed by running `get-command <name>` or `which <name>`. Without a guard, every test would fire an agent request, invisibly, and those requests would be billed. So the hook fires only on lines a human types at the prompt — not on probes made from inside a script or function. A tool check returns `command not found` the normal way; your question typed at the prompt gets an answer.\n\nDO\n\n1. $ npm install -g awsh\n you should see: awsh and aither on your PATH\n if not: Needs Node.js (nodejs.org, LTS). Open a new terminal after installing it.\n2. $ awsh init\n you should see: 'installed in <your shell profile>' - it works in PowerShell, bash and zsh\n if not: If it says the profile is not writable, it prints the exact line to add by hand. That line is the whole feature.\n3. $ awsh doctor\n you should see: a measured miss time well under the budget it prints\n if not: A slow miss names the PATH entry causing it (usually a huge directory on PSModulePath). Remove that entry; it is costing every typo a minute.\n4. type what time is it\n you should see: an answer, where 'command not found' used to be\n if not: Open a NEW terminal - the hook installs into the profile, and the profile loads at start.\n\ndone when: $ awsh doctor\n you should see: omnibox OK\n\nWHAT YOU LEARNED\n\n- The shell has exactly one response to a line it does not know; now that line reaches an agent instead.\n- It is a shell hook (CommandNotFound), not a new shell - your terminal, your directory, your environment.\n- Probes made by scripts are ignored on purpose; only what a human types at the prompt is answered.",
4
+ "category": "guide",
5
+ "description": "Chapter 9 of 9 -- Your terminal answers you. Type a question where a command would go.\n\nAbout 10 minutes. Read online: https://aitherium.github.io/awknowledge/path/09-omnibox.html",
6
+ "see_also": [
7
+ "awsh"
8
+ ],
9
+ "slug": "09-omnibox",
10
+ "status": "published",
11
+ "synopsis": "The omnibox",
12
+ "topic": "guide-09"
13
+ }
awkno/pages/guide.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "adopt": "awkno guide 1 # start at chapter 1; `awkno open guide` for the browser",
3
+ "body": "guide-00 Welcome to Aither World (~5 min)\nguide-01 Install awdk (~10 min)\nguide-02 Your first local brain (~20 min)\nguide-03 Talk to it (~15 min)\nguide-04 Build your first agent (~30 min)\nguide-05 Agent packs (~15 min)\nguide-06 Your own hardware (~30 min)\nguide-07 Deploy on awnix (~45 min)\nguide-08 Many agents, one repo (~30 min)\nguide-09 The omnibox (~10 min)",
4
+ "category": "guide",
5
+ "description": "From \"what is an agent?\" to a terminal that answers you - one path, nothing skipped.\n\nOnline: https://aitherium.github.io/awknowledge/",
6
+ "see_also": [
7
+ "guide-00",
8
+ "guide-01",
9
+ "guide-02",
10
+ "guide-03",
11
+ "guide-04",
12
+ "guide-05",
13
+ "guide-06",
14
+ "guide-07",
15
+ "guide-08",
16
+ "guide-09"
17
+ ],
18
+ "slug": null,
19
+ "status": "published",
20
+ "synopsis": "The Aither World Guide",
21
+ "topic": "guide"
22
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part I · Enforcement*\n\n**Fires when:** you write a coding standard, a convention, a \"we always…\", or a\nline in an agent rules file.\n\n## The law\n\nIf no program can fail because of your rule, your rule is a preference that\nhappens to be written down. It will be cited as binding by people who have never\nseen it enforced, and it will be quietly false long before anyone notices.\n\nThe test is one question: **what command exits non-zero when this is violated?**\nIf you cannot name it, you have not written a rule.\n\n## The measurement that produced this\n\nA quality-standards document listed seven Python rules. Every one of them was\ncited in reviews as binding. Measured against what actually ran:\n\n| rule | what was really happening |\n|---|---|\n| line length | in the linter's global ignore list — **29,902** over-length lines in the tree |\n| import ordering | per-file-ignored in every directory that holds code |\n| no swallowed exceptions | **2,746** handlers whose entire body was `pass` — no checker existed |\n| no skip inside a test body | **150** files did it — no checker existed |\n| three others | genuinely enforced |\n\n**One of seven was real.** The document had been correct on the day it was\nwritten and had rotted invisibly, because a document cannot rot loudly.\n\nThe same shape recurs everywhere once you look for it. A rules file said \"use\n`127.0.0.1`, never `localhost`\" for weeks — while the procedure file telling\nagents how to reconnect said `localhost`. An agent following the documented\nprocedure wrote the exact defect the rule existed to prevent.\n\n## The trap in the fix\n\nThe obvious repair is to turn the ignores off. Do not do that first: the tree\nopens at ~33,000 violations and the build is red forever, so the gate gets\nbypassed rather than satisfied — which is precisely how the ignore lists came to\nexist in the first place.\n\n**Gate the changed lines, not the tree and not the file.** File-scoped, a\ntwenty-line edit to a legacy module has to fix thirteen unrelated long lines\nfirst, and that gets bypassed too. The rule that survives contact with people is\n*do not add a violation*. Report the backlog separately and never gate on it.\n\nSee [LAW 11 — Open green, ratchet down](11-open-green-ratchet-down.md) for the\ngeneral form.\n\n## The check\n\nScope the rule to the diff:\n\n```bash\n# what CI runs on every pull request\npython your_checker.py --changed origin/main\n\n# the backlog - reporting only, never a gate\npython your_checker.py --all\n```\n\nRun the linter **isolated from the project config**. The ambient config is what\ndisables these rules; reading it would reproduce exactly the hole the checker\nexists to close.\n\nAnd select rules by FAMILY, not one at a time. A checker originally scoped to two\nspecific rule codes let a third violation — written the same session, in the same\nfile — go straight through it.",
4
+ "category": "law",
5
+ "description": "Law #1",
6
+ "see_also": null,
7
+ "slug": "a-rule-nothing-asserts-is-a-suggestion",
8
+ "status": "published",
9
+ "synopsis": "A rule nothing asserts is a suggestion — the first law, and the one the other seventeen depend on",
10
+ "topic": "law-01"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part I · Enforcement*\n\n**Fires when:** you find a defect and reach for the backlog.\n\n## The law\n\nRecording a defect is the cheapest possible response to finding one, and it feels\nlike work. It is not. If a program could detect the defect, then **writing the\ndetector is the discharge** — the ticket is not a smaller version of the fix, it\nis a different activity that resembles one.\n\nSort every finding into exactly one of these:\n\n| what you found | how you discharge it |\n|---|---|\n| a defect a static check could detect | **write or extend a checker + a self-test.** No ticket. |\n| a defect only a live probe can see | **add it to something scheduled that pages.** No ticket. |\n| genuinely one-off, not generalisable | a ticket — this is what a backlog is for |\n| needs a human decision, or is legal/commercial | a ticket |\n| you fixed it | a commit. Not a ticket. |\n\n## The measurement\n\nA debt ledger ran for nineteen days under a \"record everything\" rule. Result:\n**~79 rows added per day against a paydown rate of ~4.2 per day** — a 19:1\ndeficit. The ledger stopped being a queue and became a write-only archive:\n\n- 1,589 rows total\n- **664 of them already declared themselves resolved** in an open table, because\n nobody had moved them\n- the only automated consumer could see 154 of them\n- **one defect class alone accounted for 123 rows** — the same mistake, made\n again, recorded again, for weeks\n\nThose 123 rows are the whole argument. Every one was a human re-deriving a\nprocedure that had already been performed correctly at least once. A check would\nhave cost an afternoon and closed all of them.\n\n## The status field is machine-read, or it is decoration\n\nIf you do keep a ledger, its state must be parseable. The archive above\naccumulated **~1,200 distinct status strings** — \"unverified\", \"mostly done\",\n\"suspect\", \"may be fixed\" — which is how the automated sweeper came to be blind\nto 90% of its own queue.\n\nPick four words: `open` · `fixing` · `resolved` · `refuted`. First word on the\nline; prose after it if you like.\n\n## Prefer extending a checker to adding one\n\nA checker family reaches the edge of what anyone will maintain at around twenty\ntools. Design them multi-rule from the start — one tool asserting six invariants\nover one subsystem beats six tools, and it is the only shape that stays\nmaintained.\n\n## The check\n\nAsk, before you open the backlog file:\n\n> *Would a check have caught this?*\n\nIf yes, the check IS the work. And when there is genuinely nothing to record, say\nso explicitly — \"checked, no new debt: <one line why>\". That is a valid answer.\nSilence is not.",
4
+ "category": "law",
5
+ "description": "Law #2",
6
+ "see_also": null,
7
+ "slug": "make-it-a-check-not-a-ticket",
8
+ "status": "published",
9
+ "synopsis": "Make it a check, not a ticket — a mechanically-detectable defect must never be discharged by writing it down",
10
+ "topic": "law-02"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part I · Enforcement*\n\n**Fires when:** you finish writing any checker, test, or automated assertion.\n\n## The law\n\nEvery checker ships with a `--self-test` that **deliberately breaks the thing it\nasserts and proves the checker goes red.** Not a unit test of its helpers — a\ndemonstration that the alarm makes noise.\n\nWithout it you have a program that exits 0, and you are inferring that it exits 0\n*because the invariant holds*. That inference has been wrong every time anyone\nhas bothered to check.\n\n## Why this is not paranoia\n\nThree real ways a checker returns a clean pass while asserting nothing:\n\n**It scanned nothing.** A publish gate printed `0 files scanned … OK` and exited\n0 on a public repository. Reading the exit code, it passed. Reading the count, it\nhad never examined a file. An empty tree is the cleanest possible pass.\n\n**It looked in the wrong place.** A workflow checker resolved its repository root\nby walking up a fixed number of directories. That landed one level too shallow,\nin a subdirectory that happened to hold 8 workflow files. It reported a clean\npass over 8 while the 102 real ones went unexamined. See\n[LAW 8](08-a-checker-in-the-wrong-place-found-nothing.md).\n\n**Its rule was vacuous.** One rule read an optional list that, on the real tree,\nwas always empty — so it could never fire. That is a hole dressed up as coverage,\nand it is worse than no rule, because now the dashboard is green for a reason.\n\n**The number:** when a hygiene checker was first pointed at its own family, four\ndocumented-and-wired gates had no self-test at all, and the family's headline\ncount in the docs was short by roughly 6x.\n\n## What a self-test must actually do\n\n```\n--self-test must:\n 1. construct a fixture that VIOLATES the invariant\n 2. run the real rule against it\n 3. exit non-zero if the rule did NOT fire <- the assertion\n 4. construct a fixture that SATISFIES it\n 5. exit non-zero if the rule DID fire <- the anti-flood assertion\n```\n\nStep 5 is not optional. A rule that fires on everything passes step 3 perfectly\nand is useless — see [LAW 10](10-a-gate-that-floods-gets-switched-off.md).\n\n## The corollaries\n\n- **Exit non-zero when you cannot run.** A probe that could not judge is dead,\n not passing — [LAW 6](06-a-check-that-cannot-run-must-not-pass.md).\n- **Wire it somewhere unattended.** A checker that runs only when someone\n remembers to read a rules file is documentation, not enforcement. In one audit,\n exactly **1 of 22** checkers ran anywhere unattended.\n- **Print the allowlist on every run.** Any exception list must be visible each\n time and every entry must name a reason, or it quietly grows into the hole the\n gate was built to close.",
4
+ "category": "law",
5
+ "description": "Law #3",
6
+ "see_also": null,
7
+ "slug": "watch-your-gate-fail",
8
+ "status": "published",
9
+ "synopsis": "Watch your gate fail — a checker nobody has seen fail is not a gate",
10
+ "topic": "law-03"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part I · Enforcement*\n\n**Fires when:** a test passes and you are about to believe it.\n\n## The law\n\nBreak the code on purpose and confirm the test goes red. If it stays green, the\ntest is not testing what its name says.\n\nThis is [LAW 3](03-watch-your-gate-fail.md) applied one level down, and it catches\na distinct failure: **a test that passes for the wrong reason.**\n\n## The one that produced this law\n\nA retry helper had to distinguish a transport-layer timeout (retry it) from a\ngenuine command failure (do not). One test asserted \"a successful run is never\ntreated as a flap\":\n\n```python\n# the fixture, first version\nfine = CompletedProcess(args=[\"run\"], returncode=0, stdout=\"rows\\n\", stderr=\"\")\nassert not is_transport_timeout(fine)\n```\n\nIt passed. Then mutation testing deleted the `returncode == 0` guard from the\nfunction under test — and the test **still passed**.\n\nThe reason is obvious afterwards and invisible while writing it: the fixture's\nstreams held no timeout marker, so with or without the guard there was nothing to\nmatch either way. The test never exercised the branch it was named after. Fixed:\n\n```python\n# the fixture CARRIES the timeout marker on purpose, so ONLY the\n# returncode == 0 guard can save it\nfine = CompletedProcess(args=[\"run\"], returncode=0, stdout=\"rows\\n\",\n stderr=\"warning: transport timeout seen earlier, recovered\\n\")\n```\n\nNow deleting the guard fails the test. That is the difference between a test and\na decoration.\n\n## Where this bites hardest\n\n**Anything fail-closed.** A gate that denies everything passes every \"does it\ndeny?\" test perfectly while being completely inert. One licence gate answered\n\"not permitted\" for **31 of 35** entries — because 31 had no record at all and\nthe resolver failed closed. Every denial test was green. The gate was doing\nnothing. See [LAW 17](17-fail-closed-then-prove-the-happy-path.md).\n\n**Anything that returns an empty collection.** Empty is the universal disguise.\nAn unauthorised call, a wrong key, a mismatched identifier, and \"there genuinely\nare none\" all look identical downstream.\n\n**Anything with a real-world encoding.** A matcher was written against a plain\nASCII fixture. The real stream was UTF-16LE, so decoded as UTF-8 the marker\narrived with a NUL between every character — and the ASCII fixture passed against\na matcher that could not read the real thing at all. **Build the fixture from a\ncaptured real sample, not from what you assume the real sample looks like.**\n\n## The practice\n\nFor each assertion you care about, ask: *what single line could I delete from the\nimplementation and still see this pass?* Delete it. Watch. Put it back.\n\nThree mutations per critical assertion is a reasonable budget. Catching one is\nworth more than a hundred new tests, because one of your existing tests was lying\nand you now know which.",
4
+ "category": "law",
5
+ "description": "Law #4",
6
+ "see_also": null,
7
+ "slug": "mutate-the-test-not-just-the-code",
8
+ "status": "published",
9
+ "synopsis": "Mutate the test, not just the code — a fixture that cannot fail proves nothing",
10
+ "topic": "law-04"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part II · Silence*\n\n**Fires when:** always. This is the law that decides what you monitor.\n\n## The law\n\nThe failures that cost days do not throw. They return 200. They render. They log\nnothing, fail no test, and leave the container healthy. **A missing thing is\nindistinguishable from a thing nobody wanted** — and no amount of care finds it,\nbecause there is nothing to notice.\n\nSo: for every feature, ask what its *absence* would look like. If the answer is\n\"exactly like normal operation\", you must write a check, because vigilance\ncannot cover it.\n\n## Eleven silences, all real, none of which raised\n\n| what happened | what every signal said |\n|---|---|\n| A search API returned `count: 0` for an internal caller that named no tenant | **HTTP 200.** The collection held 5,462 documents. |\n| An agent's escalate-to-human tool wrote a log line and returned `status: logged_locally` | success — nothing was raised and nobody was told |\n| A worker was killed for memory mid-generation | no message, no error event; the UI said \"generating\" until the tab closed |\n| A route was missing from an access manifest | **200 to bare `curl`, 403 to every authenticated user** — the gate runs only once a session exists |\n| An extension's kill switch was written by a script as internal coordination | popup said \"Connected — 5/5 services\" for weeks |\n| A capability was declared by a package and bound to nothing | the agent behaved as though the feature was configured off |\n| A desktop app was listed in a registry and present in no renderer | a window opened saying \"not installed here\" |\n| Login sessions were written to a node-local store instead of the shared one | 200, cookie set, works perfectly — until a second node serves that user |\n| A public installer URL served a web page | `curl -fsSL` does not fail on a 200, so the one-liner piped HTML into `bash` |\n| A blocking call landed on a shared event loop | `/health` answered in ~15ms *between* the stalls; the turn eventually succeeded with a checkmark |\n| A publish pipeline's lint job went red | the push reported green; nothing shipped for ~28 hours and it was found by hand |\n\n## The three shapes\n\nAlmost every silence is one of these:\n\n1. **An empty collection.** Returned by success and by five kinds of failure.\n Fail-closed paths are especially good at this — see [LAW 17](17-fail-closed-then-prove-the-happy-path.md).\n2. **An absent event.** Nothing arrives, so nothing handles it. The UI waits\n forever, which looks like slowness rather than death. Bind `onerror` *and*\n `onmessageerror`; arm a deadline when you enter a waiting state.\n3. **A green signal from the wrong subject.** The healthcheck, the exit code, the\n container status and the dashboard are all reporting on something adjacent to\n the thing that broke — see [LAW 7](07-the-symptom-names-the-innocent.md).\n\n## The check\n\nTwo habits close most of it:\n\n**Assert the positive, live.** Every feature needs one check that the happy path\n*produces data* — not that the unhappy path produces none. A suite that only\nasserts denials is blind to a completely inert feature.\n\n**Probe the surface, not the pipeline.** A red CI job proves a job failed, not\nthat users are affected; a green one proves neither. When a publish pipeline was\nfirst diagnosed, the first diagnosis was also wrong — in the other direction —\nbecause it was inferred from a workflow log without ever fetching the site. Fetch\nthe site.",
4
+ "category": "law",
5
+ "description": "Law #5",
6
+ "see_also": null,
7
+ "slug": "design-for-the-silence",
8
+ "status": "published",
9
+ "synopsis": "Design for the silence — the expensive failures do not raise",
10
+ "topic": "law-05"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part II · Silence*\n\n**Fires when:** writing any checker, probe, or health assertion.\n\n## The law\n\nThree outcomes, three exit codes. Never two.\n\n```\nexit 0 the invariant HOLDS — I looked and it is fine\nexit 1 VIOLATION — I looked and it is broken\nexit 2 DEAD — I could not look\n```\n\n\"I could not look\" is not \"nothing is wrong\". Collapsing exit 2 into exit 0 is\nhow *the checker is broken* gets filed as *the system is fine*, and it is the\nsingle most common way a gate family rots while the dashboard stays green.\n\n## The cases where this is the whole ballgame\n\n**The gate whose subject is its own dependency.** A paging path posted alerts to\na monitoring service. On a calm day: `page -> HTTP 200`. During the outage it\nexisted for — a host reboot that took the resolver down — the monitoring service\nwas itself restarting, so the alert died in transit and **nobody was told for\nnine hours.** The detection had worked perfectly: the probe ran, found 16\nviolations, and exited 1. The delivery is what failed. See\n[LAW 9](09-detection-without-delivery-is-not-detection.md).\n\n**The empty enumeration.** A checker that enumerates units, containers, workflows\nor skills must exit 2 when it finds none. An empty list is the cleanest possible\npass, and on the one machine where a migration is actually happening it is also\nthe most likely result.\n\n**The challenged probe.** A live checker fetched ~48 URLs and compared status\ncodes against 500. The CDN bot-challenged its default user agent and answered\n**403 to everything** — and since 403 < 500, every single URL \"passed\". A total\nblackout was indistinguishable from a perfectly clean run. Two fixes: send a real\nuser agent, **and treat a mostly-403 run as exit 2.**\n\n**The pipe that eats the exit code.** `checker.py | tail; echo $?` reports\n*tail's* status. That turned a red gate green in a transcript three times in one\nsession. Never pipe a gate. Capture the child's real return code.\n\n## The reporting rule that goes with it\n\nAnything you could not examine is **counted and printed**, never skipped in\nsilence. One tree walk skipped 26 unreadable files; if that number jumps, the\nwalk has lost coverage — which otherwise looks exactly like a clean scan.\n\nThe same applies to caps. If a run bounds its own coverage (top-N, sampling, no\nretry), it must say what it dropped. Silent truncation reads as \"covered\neverything\" when it did not.\n\n## The check\n\n```python\nif not targets:\n print(\"NOT VERIFIED: enumerated 0 targets - refusing to report a pass\")\n return 2\n```\n\nAnd in the runner that aggregates them, keep the two failures apart:\n\n```\nVIOLATION (1) the invariant is broken\nDEAD (2) a timeout, a missing tool, an unreadable host\n```\n\nBoth must page. Conflating them is how a gate family quietly stops running.",
4
+ "category": "law",
5
+ "description": "Law #6",
6
+ "see_also": null,
7
+ "slug": "a-check-that-cannot-run-must-not-pass",
8
+ "status": "published",
9
+ "synopsis": "A check that cannot run must not pass — silence is not a verdict",
10
+ "topic": "law-06"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part II · Silence*\n\n**Fires when:** an error message names a component. Especially when it names one\nyou already distrust.\n\n## The law\n\nError text is written by the *caller*, at the moment it gives up. It names what\nthe caller was trying to do, which is very often not what actually broke. Treat\nthe name in the message as a hypothesis with no evidence behind it.\n\n## Four cases, each of which cost a session\n\n**An identity service that was healthy the whole time.** Four call sites read a\nURL as `os.environ.get(\"IDENTITY_URL\", get_service_url(\"events\"))` — the default\npointed at the event bus, which serves no auth routes. So the login endpoint and\nboth halves of a device grant went to a service that could not possibly answer.\nEach proxy folded the failure into its own generic \"identity service unavailable\".\n**Identity answered a direct probe with a real device code while every surface\nreported it down**, and no log line anywhere mentioned the event bus.\n\n**A CI job that said the API was down.** A self-hosted job probed `https://` at a\nhost-published port terminated by a load balancer that speaks plain HTTP. Measured:\n`http` = 200, `https` = handshake failure. The job retried 12 times and printed\n`##[error]API is not running` — **while the API was healthy throughout.**\n\n**A decompressor named in a traceback that was never selected.** A checkpoint\nfailed to load with an assertion inside one quantisation format's decompressor.\nThe format resolution was entirely correct; that class merely *inherited*\n`decompress` from a sibling. Four hypotheses died on the coincidence. The real\ndefect was a pattern list that matched none of the module names present — a\nquestion answerable from the config file in 200ms, on no hardware. The wrong\nreading cost **42.5 hours of rented 8-GPU time and produced 48 bytes.**\n\n**A maintenance page.** A public login served \"both identity nodes are\nreconnecting\". Both identity nodes were healthy. The 502 came from a routing rule\npointing at an origin over the wrong scheme, folded into a *designed* error page\nwhose text named the innocent service by hand.\n\n## Why this shape is so common\n\nA generic fallback message is written once, early, by someone who imagined only\none failure mode. Then every other failure gets routed into it. The message ages\ninto a lie that reads as a diagnosis.\n\n## The check\n\nWhen an error names a component, do these in order and stop at the first\nsurprise:\n\n1. **Probe the named component directly**, with the same scheme, port and header\n the caller uses. If it answers, the caller is wrong about who it is talking to.\n2. **Read the caller's resolved target** — not the config, the *value at runtime*.\n Env-var defaults and fallback chains are where this lives.\n3. **Check the scheme.** Plain HTTP into a TLS listener closes the socket and\n reads as \"the service is down\". TLS into a plain listener hangs. Neither error\n names the scheme.\n\nAnd when you write the message, name what you *called*, not what you assume it\nwas: `POST https://auth-host:9443/auth/device -> connection refused` is a diagnosis.\n\"Identity unavailable\" is a guess someone else will inherit.",
4
+ "category": "law",
5
+ "description": "Law #7",
6
+ "see_also": null,
7
+ "slug": "the-symptom-names-the-innocent",
8
+ "status": "published",
9
+ "synopsis": "The symptom names the innocent — follow the wire, not the error text",
10
+ "topic": "law-07"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part II · Silence*\n\n**Fires when:** a checker resolves a root, enumerates a directory, or decides\nwhat its own scope is.\n\n## The law\n\nScope is an assertion, and it needs the same skepticism as the rule itself. A\nchecker pointed at the wrong tree returns exit 0 with a confident summary, and\nthere is no observable difference between *\"I examined 102 files and they are\nfine\"* and *\"I examined the wrong 8 files\"*.\n\n**Print what you scanned. Every run. As a count.**\n\n## Four ways scope goes wrong\n\n**A fixed number of parent directories.** `parents[2]` resolved one level too\nshallow, into a subdirectory that happened to contain its own `.github/workflows`\nwith 8 files in it. Clean pass over 8; the 102 real workflows were never opened.\n**Resolve a repository root by walking up to `.git`, never by counting.**\n\n**Enumerating the wrong runtime.** A deploy checker shelled the `docker` binary\nwhile the fleet ran a different container engine. It reported `NOT VERIFIED` on\nevery single run for weeks — which was the *correct* contract, and which read as\n\"the checker is broken\" rather than \"nobody is asserting this\". A gate that\nalways says it could not look is not a gate.\n\n**Looking only where declarations are supposed to live.** A parity checker read\nevery unit file in the declarative directory. A service deployed as a hand-typed\nunit *outside* that directory was therefore **not judged clean — it was not\njudged at all.** It carried 6 of 22 environment keys and 1 of 3 volumes relative\nto its own source of truth, and the rule written for exactly that defect could\nnot fire, because there was nothing there for it to read.\n\n**Probing only the root path.** A routing checker fetched `/` for every hostname.\nOne host answered **404 at the root** (a pass) while its most specific route — the\none carrying all the real traffic — was a hard 502 for every method. Path-scoped\nrules are the ones that matter most and the ones a root probe cannot reach.\n\n## The general form\n\n> An absence and a clean result produce the same exit code unless you make them\n> produce different output.\n\nThis is [LAW 5](05-design-for-the-silence.md) turned on the tooling itself, and\nit is why [LAW 6](06-a-check-that-cannot-run-must-not-pass.md) insists on exit 2.\n\n## The check\n\nThree lines in every checker:\n\n```python\ntargets = discover()\nprint(f\"scanned {len(targets)} {noun} under {root}\")\nif not targets:\n return 2 # an empty scope is never a pass\n```\n\nDiscover by **git**, not by a hardcoded path, wherever you can — `git ls-files`\nknows about the tree you actually ship, and it will not silently miss a\ndirectory somebody moved. And when a checker's own scope changes, re-run it\nagainst the *pre-change* tree: if it does not reproduce the original defect, your\nnew scope is wrong.",
4
+ "category": "law",
5
+ "description": "Law #8",
6
+ "see_also": null,
7
+ "slug": "a-checker-in-the-wrong-place-found-nothing",
8
+ "status": "published",
9
+ "synopsis": "A checker in the wrong place found nothing — and that is indistinguishable from a clean pass",
10
+ "topic": "law-08"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part II · Silence*\n\n**Fires when:** you build anything that is supposed to tell a human something.\n\n## The law\n\nEvery gate in this codex assumes an answer to one question nobody checks: **when\na gate fails, does anyone find out?**\n\nAn alert that reaches only a dashboard has paged nobody — the dashboard is on the\nmachine that is on fire.\n\n## The nine-hour outage where detection worked perfectly\n\nA host rebooted. A DNS service lost a race for its own static address, the tunnel\ndaemon could not resolve the edge, and every public hostname served 503 while 129\ncontainers reported `Up` and healthy.\n\n**Detection was flawless.** The scheduled probe ran at 05:06, found 16 violations\nand 50 dead checks, and exited 1. Here is the whole story in two log lines:\n\n```\n[01:34] page -> monitoring HTTP 200 (host-direct) <- paged fine on a calm day\n[05:06] PAGE FAILED:... NOT DELIVERED <- the outage, unreported\n```\n\nIt failed exactly when it was needed, and that was **structural**:\n\n- **The primary required the monitoring service to be up.** In a reboot-induced\n outage the monitor is itself restarting. The primary path shares a failure\n domain with its own subject, so it is *guaranteed* unavailable in the one\n scenario that matters.\n- **The fallback was worse.** It shelled a container engine that had been\n replaced, named a container that had been renamed, used a network prefix that no\n longer existed, and **pulled a curl image from a registry — during a network\n outage.** A fallback whose dependencies are a strict superset of the primary's\n is not a fallback.\n- **A fifth trap:** the monitor answered `https` on a calm day and `http` after\n the reboot. Pinning either spelling is a latent outage; try both.\n\nNine hours. The only signal was the owner opening a browser.\n\n## The corollary about breakers\n\nThe same incident had a circuit breaker that never tripped. It banked a failure\nonly *after* a 150-second wait the process never survived — so its failure counter\nsat at 0, last written 50 days earlier, while the attempt counter climbed to 43.\n\n**A breaker that never trips is indistinguishable from one that never needed to.**\nAssert the counter moves.\n\n## The check\n\nOne rule, and it must post through **the real function**, not a hand-rolled\nrequest. A probe posting its own request proves only that the sink accepts\nrequests, which was never in doubt.\n\n```\nPPD001 a page sent through the production page() function LANDS in the sink\n - asserted by ROUND TRIP, never by return code\n - page() returns True on any 2xx, and accepted is not recorded\nPPD002 the alert can LEAVE THE BOX\n - an alert that reaches only the local dashboard has paged nobody\n```\n\nAnd the constraint that makes this a design decision rather than a config change:\n**a total-outage page must egress independently of the fleet it reports on.** With\nno working resolver, an email path could not have resolved its own relay either.\n\n> Redundancy that is never exercised is not redundancy. It is two broken paths\n> instead of one.",
4
+ "category": "law",
5
+ "description": "Law #9",
6
+ "see_also": null,
7
+ "slug": "detection-without-delivery-is-not-detection",
8
+ "status": "published",
9
+ "synopsis": "Detection without delivery is not detection — the alert path is the one thing you cannot verify by using it",
10
+ "topic": "law-09"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part III · Adoption*\n\n**Fires when:** a new rule's first run produces a lot of findings.\n\n## The law\n\nA rule's job is to be *acted on*. False positives do not merely waste time — they\nteach people that this rule's output is noise, and that lesson generalises to\nevery rule you write afterwards. **A rule at 60% precision is worse than no rule**,\nbecause the 40% trains everyone to skim.\n\nEvery per-file ignore list in every mature codebase is a fossil of this. Somebody\nturned a rule on, it flooded, and instead of narrowing the rule they silenced it\nper directory — permanently, invisibly, for every file added since.\n\n## Five first runs, and what was wrong with them\n\n| rule | first run | after narrowing | what was wrong |\n|---|---:|---:|---|\n| focus-stealing scheduled tasks | 7 false | 0 | classified by executable NAME; fixed by reading the PE subsystem header |\n| manual-toil detection | 47 | 12 | inline heredocs collapsed every ad-hoc script into one meaningless row; shell keywords named findings after a *loop variable* |\n| unshipped npm access flags | 5 (3 wrong) | 2 | it read the comment *explaining* the defect as the defect |\n| dead tunnel origins | 23 (~22 wrong) | 17 | the container listing answered inconsistently under load — 40 on one read, 2 on the next |\n| dropped environment keys | 939 | ~0 gated | keys arriving via an env-file were counted as dropped; **a number four times too large is worse than no number** |\n\nNote the second column: every one of those rules survived. Narrowing is not\nweakening.\n\n## The recurring false-positive sources\n\n1. **Comments and prose.** These files document their own past defects at length.\n Flagging the documentation of a defect as the defect is how a gate gets\n deleted rather than satisfied. **Strip comments before matching** — and anchor\n the stripper: a naive block-comment regex treats a host-permission string like\n `\"http://*/*\"` as opening a comment and eats the rest of the file.\n2. **Ordinary English.** `lib` and `apps` are words. Anchor on *syntax* — an\n import statement, a module invocation — never on a bare noun.\n3. **The rule matching itself.** A checker's own rule text contains the pattern\n it looks for. Exclude yourself explicitly; this happens on roughly every third\n new rule.\n4. **A flaky source of truth.** If your inventory command disagrees with itself\n between two runs, you cannot build a rule on a diff of it. Retry, or pick a\n different question.\n\n## The escape hatch, and its one condition\n\nWhere a violation is sometimes legitimate, allow an inline suppression — **with a\nmandatory reason**:\n\n```python\ntime.sleep(2) # blocking-ok: boot path, before the loop starts\n```\n\nA bare `# blocking-ok` must NOT suppress. That single condition turns a silent\nbypass into a visible decision, and it is the difference between an escape hatch\nand a hole.\n\n## The check\n\nYour `--self-test` needs both halves ([LAW 3](03-watch-your-gate-fail.md)):\na fixture that must fail, **and a fixture that must pass**. The second is the\nanti-flood assertion. Write a piece of prose containing your keywords and assert\nthe rule stays quiet on it.",
4
+ "category": "law",
5
+ "description": "Law #10",
6
+ "see_also": null,
7
+ "slug": "a-gate-that-floods-gets-switched-off",
8
+ "status": "published",
9
+ "synopsis": "A gate that floods gets switched off — being right too loudly is how a rule dies",
10
+ "topic": "law-10"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part III · Adoption*\n\n**Fires when:** you want to enforce something on a tree that already violates it\nhundreds of times.\n\n## The law\n\nYou cannot go from 41 violations to zero in the commit that adds the rule. And a\ngate that is red on day one is a gate someone disables on day two.\n\nSo **pin the current count and let it move in one direction only:**\n\n```python\n# Measured on the day this rule was written. Ratchets DOWN only:\n# - a higher count is new drift and fails immediately\n# - a lower count means remediation landed WITHOUT lowering the pin,\n# which also fails, so a win cannot be banked silently\nPRIVATE_LADDER_PIN = 40\n```\n\nBoth directions matter. The second is the one people forget, and it is what\nseparates a ratchet from a high-water mark: if fixing five violations does not\nrequire you to edit the pin, the next five regressions are free.\n\n## Why the equality, not an inequality\n\nPins in production use `count == PIN`, not `count <= PIN`:\n\n- **higher** — new sprawl, fail now, name the new items\n- **lower** — remediation landed; lower the pin *in the same commit*, or the\n ground you gained is given back the first time someone regresses\n\n## What a pin looks like in practice\n\nReal ones, all live:\n\n| what is pinned | at | why it cannot be zero yet |\n|---|---:|---|\n| private copies of one shared helper | 40 | migrating 40 gates is its own project |\n| jobs on a runner label that cannot run | 234 | most of a CI estate; the fix is per-workflow |\n| catalogued models with no licence record | 31 | each entry needs a human to read a licence |\n| skills waiting to be ported to the public pack | 21 | 14 of them import monorepo-only packages |\n| declared capabilities that bind to nothing | 76 | shrinks with each pack fixed |\n\nNote the last column. **A pin must name why it is not zero**, or it is\nindistinguishable from an abandoned rule.\n\n## The failure mode this replaces\n\nBefore pinning, the same information existed as a probe that printed\n`LOCAL BUT UNDISTRIBUTABLE: <36 names>` and **always exited 0**. That is a\nmeasurement, not a gate. Two things went wrong with it:\n\n1. The number never moved — which is what a difference-without-a-decision always\n does.\n2. It could not distinguish **\"not done yet\"** from **\"must never be done\"**.\n Fifteen internal runbooks sat in that list next to genuinely publishable\n skills, indistinguishable on every run. **The list read as a TODO, so the\n action it invited was publishing one of them** — a disclosure dressed as\n progress.\n\nThe fix was to record the decision per item, with a reason, and gate on the\ndecision rather than on the count alone. A reasonless exception exits non-zero:\na hole dressed up as a decision is still a hole.\n\n## The check\n\n```python\nif actual > PIN:\n fail(f\"{actual - PIN} new violations - the pin is {PIN}\")\nif actual < PIN:\n fail(f\"remediation landed ({actual} < {PIN}) - lower the pin in this commit\")\n```\n\nPrint every pinned item on every run. A list nobody sees is a list that grows.",
4
+ "category": "law",
5
+ "description": "Law #11",
6
+ "see_also": null,
7
+ "slug": "open-green-ratchet-down",
8
+ "status": "published",
9
+ "synopsis": "Open green, ratchet down — a gate that opens red gets bypassed, not satisfied",
10
+ "topic": "law-11"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part III · Adoption*\n\n**Fires when:** you write a count, a list, or a \"which of these does X\" into any\ndocument an agent or a human will act on.\n\n## The law\n\nA measured number is true on the day it is measured and is drifting from that\nmoment. If a document states a fact about the running system, **something must\nre-derive that fact and fail when the document is wrong.**\n\nDo not hand-edit a measured list. Run the tool and paste what it measured.\n\n## The list that was rewritten three times in one day, wrongly each time\n\nA rules file carried the single most consequential operational fact in the\ncodebase: *which services pick up a code change on restart, and which need a\nrebuild.* Get it wrong and you either waste 45 minutes rebuilding, or you claim a\nfix is live when it is not.\n\nIt was corrected three times in one day and was wrong all three times, because\neach pass measured a subset:\n\n- the first checked one mount destination — missing six services that use another\n- the second checked two — still missing the third\n- the third used exact-destination matching, which is **blind to a parent mount**:\n one service mounts the whole parent directory, delivering the code without\n matching any of the three destinations. So the doc said \"rebuild to deploy\"\n while a plain restart shipped a fix to it — proven live that day, 52s to 22ms,\n by restart alone.\n\nAnd the checker that was supposed to assert the whole thing shelled a container\nengine the fleet no longer ran, so it printed `NOT VERIFIED` on every run instead\nof catching any of it. See [LAW 8](08-a-checker-in-the-wrong-place-found-nothing.md).\n\n## The three ways a number rots\n\n**The world moved.** Services get added, renamed, retired. Any list of \"the N\nthat do X\" starts decaying the moment it is written.\n\n**The counting rule changed.** The number can be wrong while the world is\nunchanged, because your definition was incomplete — as above. When a count\nchanges, always ask which of the two moved.\n\n**Nobody re-ran it.** A count published as a fact (\"confirmed: production port\n3100\") was simply false when checked. The worst finding of one documentation\naudit was a checklist item with a checkmark next to it.\n\n## Make the document machine-readable on purpose\n\nIf a gate must assert a line in a document, that line's **spelling** becomes part\nof the contract. One checker parsed a doc for `The \\d+:` to find an enumerated\nlist; a later rewrite phrased it differently, and half the gate went silently\nvacuous — passing, asserting nothing, for weeks.\n\nSo: pin the phrasing, and have the checker say which line it read.\n\n## The check\n\nFour questions a documentation gate should answer, all of them cheap:\n\n```\n1. broken links zero tolerance\n2. paths that no longer resolve baseline-gated\n3. published figures RE-DERIVED from source, exact match\n4. host-port ownership read from the deployment config\n```\n\nRules 3 and 4 exist because the audit that produced them found a confirmed,\ncheckmarked, published number that was false. **Never hand-edit a number to make\nthe check pass. Re-count.**",
4
+ "category": "law",
5
+ "description": "Law #12",
6
+ "see_also": null,
7
+ "slug": "measure-it-again",
8
+ "status": "published",
9
+ "synopsis": "Measure it again — every number in your docs is decaying right now",
10
+ "topic": "law-12"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part IV · Deployment*\n\n**Fires when:** you are about to say \"that's fixed\" or \"that's live\".\n\n## The law\n\nThe code you wrote, the code in the image, and the code the process is executing\nare three different things. Every cheap signal you have confirms the first one.\n\nBefore claiming a change is live, ask the **running process** — not the file, not\nthe commit, not the build log.\n\n## The four gaps, each of which has independently cost a day\n\n**1 · The build succeeded and shipped nothing.** A function was added, committed,\nsynced, and built with an exit-0 build. The image contained **zero occurrences of\nit**, because the layer that copies source was cached. The endpoint 404'd while\nevery signal said deployed. Bust the cache with a varying build argument — and\nnote that declaring the argument without ever *varying* it pins it to a constant\nforever, so the cache-bust is inert while looking present.\n\n**2 · The build tagged something nothing runs.** A rebuild produced\n`localhost/service:latest`. The deployment unit ran `registry.example/service:latest`.\nBuild reported success, restart reported success, **and the old image kept\nserving.** Neither command was wrong; they were talking about different images.\n\n**3 · A mount makes the FILE current, not the PROCESS.** This is the subtle one.\nA live-mounted directory means your edit is visible inside the container\ninstantly — but a long-running process **never re-imports**. So:\n\n- the file inside the container shows your fix\n- the mount in the inspect output confirms it is live\n- the healthcheck is green\n- the process is still running the old code\n\nEvery cheap verification passes at once. One investigation burned five hypotheses\non an innocent service because the actual writer had imported the module **87\nminutes before** the fix reached that file. The same class had been written down\nin prose hours earlier, and prose did not stop the recurrence.\n\n**4 · Two replicas on one mount disagree.** Restart one and not the other and\nthey run different code from the same files, indefinitely. **Restart both halves\nof a pair.**\n\n## The rule of thumb that replaces guessing\n\nDo not memorise which services bake and which mount — that list decays\n([LAW 12](12-measure-it-again.md)). Ask the container:\n\n```bash\n# does THIS container mount the tree I changed, or bake it?\n<engine> inspect <container> \\\n --format '{{range.Mounts}}{{.Destination}}={{.Source}} {{end}}'\n```\n\nCheck **every** destination the tree could arrive at, and remember that a mount\nof a *parent* directory delivers the child without matching any of them.\n\n## The check\n\nTwo gates, and they are twins:\n\n```\nstaleness for MOUNTED code: is any file newer than the process start time?\n -> restart every container it names\nhash-match for BAKED code: hash the file in the image against the source\n -> IDENTICAL is proof; anything else is UNKNOWN, never \"not deployed\"\n```\n\nThat asymmetry is deliberate. A hash match is sound proof the file is live; a\nmismatch has a dozen innocent explanations. Do not report a negative you cannot\nsupport.\n\n**Read a non-zero staleness result correctly:** this is a *pre-claim* gate, not a\nfleet invariant to hold at zero. On a busy tree, other people's live edits will\nstale containers constantly. Use it as *\"I changed X — which running processes\nstill have the old X?\"*, restart those, and require zero for **them**.",
4
+ "category": "law",
5
+ "description": "Law #13",
6
+ "see_also": null,
7
+ "slug": "written-is-not-deployed",
8
+ "status": "published",
9
+ "synopsis": "Written is not deployed — ask the running thing, never the source",
10
+ "topic": "law-13"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part IV · Deployment*\n\n**Fires when:** you add a new file, a new package, or a new asset that something\nreads at runtime.\n\n## The law\n\nWriting a file puts it on your disk. Nothing more. Whether it reaches a build, a\nclone, an image or a published package is a separate question with a separate\nanswer, and every local signal — the tests, the type checker, the dev server —\nanswers the first question while you are asking the second.\n\n**A file is not tracked because you wrote it. It is tracked when `git ls-files`\nsays so.**\n\n## Four ways a file you wrote does not ship\n\n**An unanchored ignore pattern.** A rule written for build artifacts — `logs/`,\n`data/`, `build/`, `secrets/` — applies at **every depth**, so it also matches\nidentically-named *source* directories anywhere in the tree. Thirty-one source\nfiles were found excluded from every clone this way. One of them was a data file\nthat shipped code read at runtime, so the published package raised\n`FileNotFoundError` on first use. Nothing local can see it: the files are on\ndisk, so builds pass, tests pass, and `git status` is clean.\n\n**A package whose manifest is untracked.** Source committed, `package.json` not.\nIt resolves locally through the workspace symlink, so tests and type-checking\npass — and then every image build dies with `Module not found`.\n\n**An entry point that only exists at publish time.** Packages can declare one set\nof entry points for local development and another that the registry swaps in on\npublish. That means **the paths that actually ship are exactly the ones no local\nbuild, no test and no type check ever exercises.** Neither publishing nor\ninstalling resolves them, so the package publishes green, installs green, and\nevery import of it fails with an error naming the *consumer*. A published version\ncannot be taken back.\n\n**A dependency resolved by a build alias instead of declared.** A shared UI\npackage was pulled in by a bundler alias rather than a dependency entry — so\nevery \"is this dependency vendored?\" check looked straight past it. It shipped\nabsent (build failure, stale bundle served for two days), then shipped **stale**,\none commit behind. The stale half is invisible: the app passed a prop to a\ncomponent that no longer took that prop, and the framework **discards an unknown\nprop without warning.** Build green, bundle valid, page renders, feature gone.\n\n## The general shape\n\nEvery one of these is the same: **the artifact you tested is not the artifact you\nshipped.** Local resolution is more forgiving than published resolution, in four\nindependent ways, and each forgiveness is a place a defect hides.\n\n## The check\n\n```bash\n# the only authority on what ships\ngit ls-files <path> | head\n\n# and for a package, resolve the PUBLISH entry points, not the local ones\npython your_checker.py --package <dir>\n```\n\nRun entry-point checks **after a build**, and have the checker say so when it\ncannot judge — a package with a build script and no output directory has not been\nverified, and reporting that as a pass is exactly the vacuous result\n[LAW 6](06-a-check-that-cannot-run-must-not-pass.md) forbids.\n\nOne more, learned the hard way: **a green check proves the files are there, never\nthat the build put them there.** One package went green because somebody had\ncopied the missing assets in by hand. Delete the output directory, rebuild, and\ncheck again before you believe it.",
4
+ "category": "law",
5
+ "description": "Law #14",
6
+ "see_also": null,
7
+ "slug": "you-wrote-it-that-does-not-mean-it-ships",
8
+ "status": "published",
9
+ "synopsis": "You wrote it; that does not mean it ships — a file is tracked when the tooling says so",
10
+ "topic": "law-14"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part IV · Deployment*\n\n**Fires when:** you are about to duplicate a file, a rule set, or a helper\nbecause the two places cannot import from each other.\n\n## The law\n\nTwo copies of anything drift. Not *might* drift — do drift, on a schedule set by\nhow often either side changes. The only two acceptable states are:\n\n1. **one copy**, imported by everyone; or\n2. **one source and N generated mirrors**, with a checker that diffs them.\n\nA comment saying \"keep these in step\" is not a third option. It has been tried.\n\n## The evidence\n\n**A shared browser worker.** One copy had been hardened over months with error\nhandlers, device-loss handling and a timeout breaker. The shared copy — used by\nfour other products — had **none of it**. The file carried a comment saying\nexactly this: *\"the fix never reached the shared copy. Keep them in step.\"* It\ndrifted anyway.\n\n**Seventeen hand-copied modules.** A package was mirrored into a second tree by\nhand, guarded by a check that compared **exactly three functions** and reported\nOK. Measured: **14 of 17 modules had drifted.** Eleven differences were re-worded\nprose — a human re-sanitising each docstring on every copy. Two were real\nbehaviour changes. And one was worse than drift: the copy had **lost a guard**,\nso a commit touching a file with unresolved conflict markers recorded every\nfunction in it as deleted — confidently wrong data in a log meant to be\nauthoritative. The test pinning that contract had been failing with an\n`AttributeError`, which reads as a stale test rather than a missing guard.\n\n**Forty private copies of one helper.** A shared module existed precisely so a\nruntime ladder was written once. Its own docstring said *\"import it; do not paste\na fourth copy.\"* Measured: **40 files carried their own copy and 0 imported the\nshared one.** The module was a registry nothing rendered. Two corrections had\nlanded on the shared copy that month and **neither reached any of the 40** — so\neach fix benefited exactly one file.\n\n## Why you cannot always just import\n\nSometimes duplication is structural, and pretending otherwise ships a worse bug.\nOne package could not be re-exported through a shim because the build that\nproduces service images copies a different set of directories — the shim would\nhave worked perfectly on a developer machine and been a `ModuleNotFoundError` in\nevery container.\n\nSo when duplication is forced: **pick a source, generate the mirror, diff it in\nCI.** Drift then stops being representable — you regenerate, or the gate is red.\n\n## Diff the right thing\n\nTwo practical rules that decide whether the checker survives:\n\n- **Compare only what must match.** One mirror check compares the shared rule\n vocabulary; the file paths, imports and error handling differ on purpose. A\n byte-diff there would be red forever and get deleted.\n- **Normalise line endings.** One tree was CRLF and its source LF: a byte-exact\n comparison called 69 files stale where 10 were real. See\n [LAW 10](10-a-gate-that-floods-gets-switched-off.md).\n\n## The check\n\n```bash\npython your_checker.py # exit 1 on drift\npython your_checker.py --write # regenerate\npython your_checker.py --diff # show what moved\n```\n\nAnd verify the checker by mutation ([LAW 4](04-mutate-the-test-not-just-the-code.md)):\ndelete one entry from the mirrored copy and confirm the tool names that entry.",
4
+ "category": "law",
5
+ "description": "Law #15",
6
+ "see_also": null,
7
+ "slug": "generate-never-copy",
8
+ "status": "published",
9
+ "synopsis": "Generate, never copy — a comment asking people to keep two copies in step is not a gate",
10
+ "topic": "law-15"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part IV · Deployment*\n\n**Fires when:** a property is defined jointly by two or more files, and no single\none of them is wrong.\n\n## The law\n\nSome invariants have no owner. Each file that participates is locally sane, every\ntest over each file passes, and the defect exists only in the *relationship*\nbetween them — which nothing looks at, because looking at it is nobody's job.\n\n**Name the invariant, put it in one checker, and make that checker the owner.**\n\n## Five that were live, and what each cost\n\n**Pricing.** A platform billed at nine price points and entitled at three. A\n$249/month tier resolved to 39 tools while a $99 tier got 230. The plan table, the\ntier map, the tool bands and the rank ladder lived in four different modules and\n**each was individually sane.** Nothing could catch it, because a pricing defect\nis a cross-file invariant by construction.\n\n**Two container engines.** One service ran in both, and one host port was\npublished from both. Docker's healthcheck was green for its copy; the other\nengine's unit was active for its copy; **every single-engine probe passed.** One\nengine's tooling cannot see the other's containers. The defect existed only in the\nunion — and a reboot could have handed the platform's identity port to the copy\nthat could not answer.\n\n**A protocol across two languages.** An enum in Python and a union type in\nTypeScript naming the same set. Add a member to one and it renders in no lane —\nno error, no failed test, just a thing that never appears.\n\n**Two registries for one feature.** An app needs an entry in a manifest (which\n*lists* it) and an entry in an import map (which *renders* it). Thirty-five apps\ndeclared widgets nothing could render; two import entries were reachable only by\nsession restore; and one id was reserved for tenant isolation in a third file for\nan app that **existed in neither of the other two** — a file describing the\nisolation rules for something nobody had built.\n\n**Config and its transcription.** A service was migrated to a new deployment\nformat by hand. Against its own source of truth, the new unit carried **6 of 22\nenvironment keys and 1 of 3 volumes** — losing the internal CA mount, the\ninference URL, and the admin allowlist containing the only person who needed\nadmin. The gate written for exactly that dropped-CA defect could not fire,\nbecause it enumerated a directory the hand-written unit was not in\n([LAW 8](08-a-checker-in-the-wrong-place-found-nothing.md)).\n\n## The two sub-rules\n\n**A migration is a transcription, and transcription loses things silently.** The\nlast case is not a *conflict* — it is an **omission**, and an omission is\ninvisible to every rule that compares two live things. Compare the copy to the\noriginal, not the copy to itself.\n\n**Generated mirrors beat detected drift.** Parity checkers detect drift *after*\nsomeone writes it. Where you can generate one side from the other, do — then\ndrift is not representable at all. See [LAW 15](15-generate-never-copy.md).\n\n## The check\n\nWrite one tool per *invariant*, not per file, and let it grow:\n\n```\nPL001 tool counts increase monotonically with price\nPL002 throughput increases monotonically with price\nPL003 no paid tier resolves below a free one\nPL004 every plan is explicitly mapped to a tier\n...\n```\n\nKnown-open inversions go in an allowlist **inside the tool**, each naming a\nticket, printed on every run. And exit 2 — never 0 — if the tool could not import\none of the files it compares. A cross-file checker that can only read one side is\nasserting nothing.",
4
+ "category": "law",
5
+ "description": "Law #16",
6
+ "see_also": null,
7
+ "slug": "the-defect-lives-in-the-union",
8
+ "status": "published",
9
+ "synopsis": "The defect lives in the union — every file is individually correct",
10
+ "topic": "law-16"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "adopt": null,
3
+ "body": "*Part V · Trust*\n\n**Fires when:** writing or reviewing any function that decides whether something\nis allowed.\n\n## The law\n\nTwo halves, and almost everyone ships only the first.\n\n**Half one — deny on every non-happy path.** Error, `None`, empty, timeout,\nunparseable, unknown: all of them return the deny value. Any function named\n`*gate*`, `*allow*`, `*owns*`, `check_*`, `verify_*`, `require_*`, `*_ok` must\nhave no path that reaches an allow by accident.\n\n```python\nexcept Exception:\n return True # <- this is the bug. Every time.\n```\n\n**Half two — assert the happy path produces data, live.** This is the half that\ngets skipped, and it hides a total outage.\n\n## Why half one alone is a trap\n\nA fail-closed path that **always** returns empty passes every \"returns nothing\"\nassertion trivially. A test suite that only asserts denials is structurally blind\nto a completely inert feature.\n\nMeasured: a licence gate decided whether a platform could commercially serve a\nmodel, and answered `False` for any model with no record. **31 of 35 catalogued\nmodels had no record**, so that fail-closed answer was returned for nearly\neverything — permissive models included. Every \"does it deny?\" test was green.\nThe gate had never once said yes to anything.\n\n> A gate that denies everything passes every denial test while being completely\n> inert. That is the one failure mode a denial test cannot see.\n\n## The silent no-op, in four disguises\n\nSame shape, four surfaces, all live:\n\n- **A missing credential.** An internal call with no auth header 401s, and the\n universal idiom `if response.status_code == 200:` with no `else` turns a\n permanently rejected call into \"nothing matched\".\n- **A wrong body shape.** A framework drops an unknown key, then rejects the\n request for the missing required one — a 422 the caller never reads.\n- **An unnamed scope.** A vector search with no tenant filter fails **closed to\n an empty list with HTTP 200**, no error and no log. Measured: a search returned\n `count: 0` while the collection reported its 5,462 documents.\n- **An identity mismatch.** A value registered under one key and looked up under\n another. Zero matches, forever, silently.\n\nThose four had **stacked**: one knowledge pipeline had never once succeeded, and\nfixing any single break only revealed the next.\n\n## The check\n\nThe static half is mechanical — find the allow-on-error:\n\n```\n a security-named function returns the allow value from an except/None/default path\n an outbound call with certificate verification switched off\n (trust your own internal CA instead - never disable the check)\n an internal service call carrying no identity header\n```\n\nThe semantic half is a review question you answer out loud against the diff:\n\n> Which line proves the happy path returns real data, on a live system, not a\n> mock?\n\nIf there isn't one, the feature is unverified regardless of how green the suite\nis. Watch especially for cross-tenant reads: assert that the wrong tenant gets\nnothing **and** that the right tenant gets something.\n\n---\n\n*Footnote, and it is the point of [LAW 10](10-a-gate-that-floods-gets-switched-off.md):\nthe first draft of this file was **blocked by a pre-commit hook** — which matched\nthe disable-verification flag inside the paragraph telling you never to use it.\nFlagging the documentation of a defect as the defect is the most common way a\ngood rule earns its way to being switched off. Strip comments and prose before\nmatching, or accept that your rule will one day be deleted rather than fixed.*",
4
+ "category": "law",
5
+ "description": "Law #17",
6
+ "see_also": null,
7
+ "slug": "fail-closed-then-prove-the-happy-path",
8
+ "status": "published",
9
+ "synopsis": "Fail closed, then prove the happy path — a gate that denies everything passes every denial test",
10
+ "topic": "law-17"
11
+ }