BeeverForge core architecture
Repository principles
The rules the core repository holds itself to, from how a test is named to how a release is cut. Most are written in CONTRIBUTING.md and docs/RELEASING.md and enforced by a test or a workflow; where a rule is a convention rather than a check, this page says so.
Tests#
Named as sentences#
A test file states what it proves, for example test_a_card_is_closed_only_when_its_work_merges.py, so that searching the suite for a behaviour finds the test that holds it.
Both orders and in parallel#
The suite runs in random order by default (pytest-randomly) and in parallel (pytest-xdist). Contributors are asked to run it in parallel in both random and fixed order (-p no:randomly). A failure that depends on order is treated as a state leak, not as flakiness. CI runs the random order, sharded; it does not run the fixed order. The conftest records each run’s seed so an order can be replayed.
Mutation-proven guards#
tools/mutate.py runs a mutation plan: each plan names a test and a list of exact source edits, and every edit must turn that test red. It refuses to start unless each edit’s anchor matches exactly once and the baseline is green.
- The runner is crash-safe. Before it mutates a file it backs the file up outside the tree and writes an in-flight note at the repository root; it restores the file and checks the restored text in a
finallyblock. Because a killed process skipsfinally, the next run refuses to start while a note exists and prints the commands that restore the file. - Mutation plans are run by contributors, not in CI. A test checks that every plan can run.
A guard reads the thing, not text about the thing#
A test that guards a configuration executes it or parses it: it runs the script and reads its arguments, or loads the YAML and reads the key. Where text must be searched, comments are stripped first.
A hermetic suite#
- Live credentials are removed from the environment before collection and again before every test: forge, chat, engine, harness and cloud variables, and the operator’s own
BEEVERFORGE_*settings. - The suite runs under its own
HOME, with each test given a fresh one, and theghlogin hidden. The project registry is a temporary directory, fresh for every test. - No test reaches a live engine: connecting to Temporal raises, except in tests marked
owns_its_engine, which start and dispose of their own ephemeral server. - The suite commits under its own git identity.
Optional externals skip at run time#
A missing optional tool, such as Docker or node, makes a test skip when it runs; it never changes what is collected. CI sets TEST_REQUIRE_DOCKER, which turns the Docker skip into a failure there. The Temporal test server is a hard prerequisite, not a skip.
CI is held to the Makefile, and every test runs once#
- Each CI step that tests or lints runs a
maketarget (install,lint,test-collect,test-unitin four shards,test-integration,test-alone,test-ran-once). The other steps check out the code, set up the tools, move artifacts between jobs or report whether every job passed. - A guard expands the targets CI names with
make -nand checks that they still run the suite and lint every root the suite covers. No guard forbids a CI step that is not amaketarget. - The suite is collected once; every shard records the test ids it ran;
scripts/every_test_ran_once.pyfails on a test that is missing, doubled or unknown. An empty collection is a failure, not a pass.
Linters and type checking#
make lint runs ruff and shellcheck. mypy and bandit are configured in pyproject.toml and installed with the development extra, but neither runs in the Makefile or in CI; they are not enforced.
Code and documentation#
Of these three rules only the changelog fragment is checked, by a workflow. The other two are documented conventions, in CONTRIBUTING.md and docs/adr/README.md.
- Comments say why, with the measurement: the date, the exit code or the incident that made the line necessary.
- Changelog fragments. A pull request that changes behaviour adds
changes/<issue>.<type>.md, where the type is one ofhighlight,behaviour,fix,security,upgradeorlimitation. A workflow checks every pull request; one that changes only documentation or tests carries theno-release-notelabel instead. - Decision records are amended in place. When a decision moves, the ADR is edited with a dated note that names the pull request. A new ADR supersedes an old one only when the decision is reversed, not refined.
Releases and versions#
These rules are documented conventions, written in docs/RELEASING.md. The release workflow runs on any tag that starts with v and does not check them, and the install rehearsal runs after the release, not before it. What is checked is listed under Release guards.
- Versions are pre-1.0 semantic versions. A minor release carries features and changes of behaviour; a patch carries fixes only and is tagged on its
release/x.ybranch, never onmain. - A release candidate is tagged
vx.y.z-rc.Nand published as a pre-release; installs keep resolving the last final release unless the candidate is named. A minor always goes through at least one candidate. - The package declares exactly the tag without its
v, and, when PyPI publishing is enabled, the release workflow refuses a tag the package does not declare. Every version change regenerates the committed OpenAPI description in the same commit. - The final release is tagged on the commit the last candidate was verified on, plus the commit that declares the version and its notes.
- A published release is frozen: no tag is moved, deleted or re-pushed. The upgrade is to be rehearsed before every tag. Security defects follow a private advisory path. Only the release manager creates a release branch or a tag.
Release guards#
- The wheel. A test builds a real wheel from a pristine copy of the tree and checks that it ships
LICENSEandNOTICE, that its only console script isbeeverforge, that its long description is the declared page, and that every runtime data file the platform needs is inside it. - The API description.
docs/reference/openapi.jsonmust equal what the application generates.
Licence and name#
The core is licensed under Apache-2.0. There is no contributor licence agreement: the licence already settles what happens to a contribution, and copyright stays with its author. The NOTICE file separates the code from the name: the licence grants no right to the BeeverForge name, logo or marks, and only builds that pass the published conformance suite (beeverforge conformance, beeverforge conformance-adapter) may be called BeeverForge.
Architecture decision records#
The status column reflects the code at the pinned commit. Where a record’s own status line and the code disagree, the code is used: “partly implemented” means some of the decision is in the code and some is not.
| Record | Title | Status |
|---|---|---|
| ADR-0001 | Foundational conceptual model of the platform | Accepted |
| ADR-0002 | Task sizing and the execution budget | Accepted; amended by ADR-0013 |
| ADR-0003 | Autonomous merge on the current base | Accepted |
| ADR-0004 | CI-aware autonomous repair (react, don't block) | Accepted |
| ADR-0005 | Post-merge deploy watch (observe & notify, never block) | Accepted |
| ADR-0006 | Bounded review-repair loop (react to a rejection, once) | Accepted; opt-in under ADR-0014 |
| ADR-0007 | The floor frees at merge, not at agent-done (dependency safety) | Accepted; revised by ADR-0010 |
| ADR-0008 | On-demand e2e via a labelled ticket (not on every PR) | Accepted |
| ADR-0009 | Durability & resilience hardening (post-audit) | Accepted |
| ADR-0010 | Single-line strict: the queue advances only on a clean merge (park on any impediment) | Accepted |
| ADR-0011 | Suppression-repair in the sandbox + reviewer-vetted coverage pragmas | Accepted |
| ADR-0012 | Perfect resume after a rate-limit pause (token visibility + honoured backoff + session resume) | Accepted |
| ADR-0013 | Pre-flight sizing gate, autonomous split, ticket effort budget, and stuck-recovery | Accepted, partly implemented |
| ADR-0014 | Single-agent execution + advisory review (frontier-default, still agnostic) | Accepted |
| ADR-0015 | The tech-lead: impediment diagnosis + Slack, one brain, two triggers | Accepted; the chat transport is an add-on |
| ADR-0016 | Acting from Slack: read for all, write behind an allowlist | Accepted; the chat transport is an add-on |
| ADR-0017 | The Knowledge Layer: a derived, verifiable map the agent may not trust blindly | Accepted; revised by ADR-0023 and ADR-0035 |
| ADR-0018 | Three harness roles: executor, reviewer, techlead | Accepted |
| ADR-0019 | The product role: requirements are authored documents, in their own repo | Accepted; the chat transport is an add-on |
| ADR-0020 | The tech-lead is on call: classify by remedy, resolve what it can, speak while doing it | Accepted |
| ADR-0021 | Agents that follow through: the open loop as the unit of memory | Accepted |
| ADR-0022 | Provider seams: an axis is agnostic when it is BORN with two | Accepted, partly implemented |
| ADR-0023 | The map is derived, not learned: generate it where the checkout is | Accepted; revised by ADR-0035 |
| ADR-0024 | Conversational memory: the thread is the unit, the raw log is sacred | Proposed, partly implemented |
| ADR-0025 | Delivery closes with the client, not with the board | Accepted |
| ADR-0026 | A word the reader already has beats one you invent | Accepted |
| ADR-0027 | The client's board is not our test bench | Accepted |
| ADR-0028 | A "yes" is read; a "done" is not asserted | Accepted |
| ADR-0029 | A click is not interpreted | Accepted |
| ADR-0030 | What the parallel audit found, and the inversion it exposed | Accepted |
| ADR-0031 | Observing is not correcting: when the safety net becomes the damage | Accepted |
| ADR-0032 | The requirement cycle happens in the channel; merging is not agreeing | Accepted; refined by ADR-0047 |
| ADR-0033 | The lifecycle decides in pure code; the engine only executes | Proposed, partly implemented |
| ADR-0034 | Providers stay in-process for now, and the door stays open by a test | Accepted |
| ADR-0035 | The knowledge layer becomes the behaviour, not an experiment | Accepted |
| ADR-0036 | Ordering across repositories: the product declares it, the floor enforces it | Proposed |
| ADR-0037 | The box: the client's image, an injected toolbox, and a proof before any pickup | Accepted |
| ADR-0038 | The platform is complete on its own; channels are add-ons | Accepted |
| ADR-0039 | The action layer: what a human can ask the factory to do, written once | Accepted |
| ADR-0040 | The core runs on the client's own machines; a cloud is an add-on | Accepted |
| ADR-0041 | The roles read facts as files, not through a tool protocol | Accepted |
| ADR-0042 | The backfill has four inputs, and a legacy system is the product | Accepted, partly implemented |
| ADR-0043 | The distribution is a published image, and one compose file both installs and builds | Accepted |
| ADR-0044 | A project declares what it is: profiles as a cascade layer | Accepted |
| ADR-0045 | A fingerprint nothing reads is a promise, not a mechanism | Proposed, partly implemented |
| ADR-0046 | A file nothing describes is the least safe to change | Accepted |
| ADR-0047 | A requirement is agreed twice, in the conversation, and never through a pull request | Accepted |
| ADR-0048 | The factory asks before it spends, and the question goes to the person who asked | Accepted |
| ADR-0049 | The whole cycle runs on one machine: local is a kind, not a mode | Accepted, partly implemented |
| ADR-0050 | A preview before the merge, because nothing after it can be taken back | Accepted |
| ADR-0051 | One door, parallel conversations, one semaphore on work | Accepted, partly implemented |
| ADR-0052 | The owner's view: what the product is doing, and the whole map of it | Proposed, partly implemented |
| ADR-0053 | The guardian's memory: raw, evidence and curated truth, with time as data | Proposed, partly implemented |
| ADR-0054 | A card the product role opens is drafted, judged against a rubric, and shown whole before the yes | Accepted |
| ADR-0055 | A card has one lifecycle: one door for every change of its state, one table of what follows, one record of what happened | Implemented; the record reads Proposed |
| ADR-0056 | A job's prompt has one budget, and every document it leaves out is named in it | Accepted |
| ADR-0057 | Who may act is one decision the core's policy makes, and a production approval is a person, not a password | Proposed, partly implemented |
| ADR-0058 | A product owns its requirement template | Accepted |
Sources#
CONTRIBUTING.mdMakefile.github/workflows/ci.yml.github/workflows/release-note.ymlpyproject.tomltests/conftest.pytools/mutate.pyscripts/every_test_ran_once.pyscripts/release_notes.pytests/test_ci_runs_what_we_run.pytests/test_ci_runs_every_test_exactly_once.pytests/test_the_wheel_ships_what_the_platform_needs.pytests/test_the_api_is_versioned.pydocs/RELEASING.mddocs/adr/README.mdLICENSENOTICE