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 finally block. Because a killed process skips finally, 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 the gh login 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 make target (install, lint, test-collect, test-unit in 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 -n and checks that they still run the suite and lint every root the suite covers. No guard forbids a CI step that is not a make target.
  • The suite is collected once; every shard records the test ids it ran; scripts/every_test_ran_once.py fails 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 of highlight, behaviour, fix, security, upgrade or limitation. A workflow checks every pull request; one that changes only documentation or tests carries the no-release-note label 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.y branch, never on main.
  • A release candidate is tagged vx.y.z-rc.N and 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 LICENSE and NOTICE, that its only console script is beeverforge, 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.json must 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.

RecordTitleStatus
ADR-0001Foundational conceptual model of the platformAccepted
ADR-0002Task sizing and the execution budgetAccepted; amended by ADR-0013
ADR-0003Autonomous merge on the current baseAccepted
ADR-0004CI-aware autonomous repair (react, don't block)Accepted
ADR-0005Post-merge deploy watch (observe & notify, never block)Accepted
ADR-0006Bounded review-repair loop (react to a rejection, once)Accepted; opt-in under ADR-0014
ADR-0007The floor frees at merge, not at agent-done (dependency safety)Accepted; revised by ADR-0010
ADR-0008On-demand e2e via a labelled ticket (not on every PR)Accepted
ADR-0009Durability & resilience hardening (post-audit)Accepted
ADR-0010Single-line strict: the queue advances only on a clean merge (park on any impediment)Accepted
ADR-0011Suppression-repair in the sandbox + reviewer-vetted coverage pragmasAccepted
ADR-0012Perfect resume after a rate-limit pause (token visibility + honoured backoff + session resume)Accepted
ADR-0013Pre-flight sizing gate, autonomous split, ticket effort budget, and stuck-recoveryAccepted, partly implemented
ADR-0014Single-agent execution + advisory review (frontier-default, still agnostic)Accepted
ADR-0015The tech-lead: impediment diagnosis + Slack, one brain, two triggersAccepted; the chat transport is an add-on
ADR-0016Acting from Slack: read for all, write behind an allowlistAccepted; the chat transport is an add-on
ADR-0017The Knowledge Layer: a derived, verifiable map the agent may not trust blindlyAccepted; revised by ADR-0023 and ADR-0035
ADR-0018Three harness roles: executor, reviewer, techleadAccepted
ADR-0019The product role: requirements are authored documents, in their own repoAccepted; the chat transport is an add-on
ADR-0020The tech-lead is on call: classify by remedy, resolve what it can, speak while doing itAccepted
ADR-0021Agents that follow through: the open loop as the unit of memoryAccepted
ADR-0022Provider seams: an axis is agnostic when it is BORN with twoAccepted, partly implemented
ADR-0023The map is derived, not learned: generate it where the checkout isAccepted; revised by ADR-0035
ADR-0024Conversational memory: the thread is the unit, the raw log is sacredProposed, partly implemented
ADR-0025Delivery closes with the client, not with the boardAccepted
ADR-0026A word the reader already has beats one you inventAccepted
ADR-0027The client's board is not our test benchAccepted
ADR-0028A "yes" is read; a "done" is not assertedAccepted
ADR-0029A click is not interpretedAccepted
ADR-0030What the parallel audit found, and the inversion it exposedAccepted
ADR-0031Observing is not correcting: when the safety net becomes the damageAccepted
ADR-0032The requirement cycle happens in the channel; merging is not agreeingAccepted; refined by ADR-0047
ADR-0033The lifecycle decides in pure code; the engine only executesProposed, partly implemented
ADR-0034Providers stay in-process for now, and the door stays open by a testAccepted
ADR-0035The knowledge layer becomes the behaviour, not an experimentAccepted
ADR-0036Ordering across repositories: the product declares it, the floor enforces itProposed
ADR-0037The box: the client's image, an injected toolbox, and a proof before any pickupAccepted
ADR-0038The platform is complete on its own; channels are add-onsAccepted
ADR-0039The action layer: what a human can ask the factory to do, written onceAccepted
ADR-0040The core runs on the client's own machines; a cloud is an add-onAccepted
ADR-0041The roles read facts as files, not through a tool protocolAccepted
ADR-0042The backfill has four inputs, and a legacy system is the productAccepted, partly implemented
ADR-0043The distribution is a published image, and one compose file both installs and buildsAccepted
ADR-0044A project declares what it is: profiles as a cascade layerAccepted
ADR-0045A fingerprint nothing reads is a promise, not a mechanismProposed, partly implemented
ADR-0046A file nothing describes is the least safe to changeAccepted
ADR-0047A requirement is agreed twice, in the conversation, and never through a pull requestAccepted
ADR-0048The factory asks before it spends, and the question goes to the person who askedAccepted
ADR-0049The whole cycle runs on one machine: local is a kind, not a modeAccepted, partly implemented
ADR-0050A preview before the merge, because nothing after it can be taken backAccepted
ADR-0051One door, parallel conversations, one semaphore on workAccepted, partly implemented
ADR-0052The owner's view: what the product is doing, and the whole map of itProposed, partly implemented
ADR-0053The guardian's memory: raw, evidence and curated truth, with time as dataProposed, partly implemented
ADR-0054A card the product role opens is drafted, judged against a rubric, and shown whole before the yesAccepted
ADR-0055A card has one lifecycle: one door for every change of its state, one table of what follows, one record of what happenedImplemented; the record reads Proposed
ADR-0056A job's prompt has one budget, and every document it leaves out is named in itAccepted
ADR-0057Who may act is one decision the core's policy makes, and a production approval is a person, not a passwordProposed, partly implemented
ADR-0058A product owns its requirement templateAccepted

Sources#