BeeverForge core architecture
Engineering principles
The core’s principles are written down in its vision document and its decision records. This page lists them with the code that enforces each, and says where the code falls short of the statement.
Vision principles#
From docs/core/00-vision.md. They are stated aims; where the code does not hold one fully, the item says how far it goes.
- The core names no vendor. A vendor is reached through a port, chosen by a registry, from configuration. The test that enforces it covers
beeverforge/contractsandbeeverforge/policyonly; the built-in adapter rows name their vendors, and the metrics sink is inferred asdynamodbwhenBEEVERFORGE_METRICS_TABLEis set. - Everything outside the core is replaceable, by someone other than the maintainers: an entry point, not a pull request. The engine is the exception: Temporal is not behind a port.
- An application built on the core is not the core. It uses the same surface a third party’s application would.
- The core is useful by itself. The document adds that nothing that ships in it is removed to manufacture an upgrade path; that is a stated commitment, which the code cannot show.
- A contract is derived from what the core calls, never from what a provider offers.
- An axis is agnostic when it is born with two implementations, not when it declares an interface (ADR-0022). Several axes do not meet it yet; Extension model lists them.
Decisions of ADR-0001#
ADR-0001 fixes the conceptual model. Its decisions, by heading:
- D-1 The framework and the project are separated by a declarative manifest.
- D-2 Project knowledge is a cascade of authority: tighten, never loosen.
- D-3 The framework owns the slots; conformance enforces them.
- D-4 An ephemeral environment with a persistent dependency cache.
- D-5 One generic worker per ticket; executor and reviewer are separate contexts.
- D-6 The label is a guess; the diff is the truth. Components resolve stacks.
- D-7 Declared dependencies are the truth; inferred ones are advisory.
- D-8 A spec-quality gate: trust to start, verify to finish.
- D-9 Documentation has standard roles, not standard locations; ADRs are the constitution.
- D-10 Derive, do not maintain.
- D-11 Deterministic first.
- D-12 The platform is a lifecycle operator, not a pull-request opener.
- D-13 Judges are structured calls on deterministic gates; agent behaviour is measured, not assumed.
- D-14 Single-tenant, multi-project.
- D-15 Pickup, ownership and impediment handling.
- D-16 Temporal as the durable-execution engine.
- D-17 A remote task box that runs the whole job. In this repository that box is a description only; its runner is an add-on.
Invariants, checked against the code#
- Never a silent hang
- Every wait in the job workflow has a deadline or a person at the end of it: the merge watch, the production approval and an unanswered impediment have deadlines, and a decision park holds until it is answered. Parks carry the options a person can take. A pull request on which no check reports is called out, not read as green.
- Nothing lost, nothing duplicated
- Jobs are durable workflows. Pull requests are find-then-update; before rebuilding a branch the runner asks the forge for an existing pull request, and if it cannot read the answer it holds instead of guessing.
- The floor frees at merge
- The job workflow holds its slot until the pull request merges, until promotion ends when it is enabled, or until the job is skipped. A parked job keeps its slot: a plain impediment is skipped after 3 days, and a decision park waits until it is answered.
- Quality is a floor
- The
testandsecurityroles are required before the implementation pass. The floor checks that the roles exist, not what they run, and the inheritedsecuritygate is advisory. - The code is the source of truth
- The knowledge map is given to an agent only when its checksums prove it describes that job’s own checkout; otherwise nothing is given.
- Attributed to the bot
- The factory commits and acts under the bot identity the deployment sets (
BEEVERFORGE_BOT_NAME,BEEVERFORGE_BOT_EMAIL). - Least privilege
- Partly. The manifest’s
permissionsfield is accepted and marked inert: no code reads it, and per-project permission tightening is not implemented. The box’s isolation and itsbox.envare what bound a run.
One action layer for every transport#
Everything a person can ask the factory to do is written once, in the action catalogue (ADR-0039): beeverforge/actions/, executed through actions.perform. Who asked is a parameter of the call, not a property of the transport.
- The panel API serves the catalogue at
POST /api/act/{name}, and its named routes call the same function. - The command line serves it as
beeverforge act, and several of its other commands, such as adding or removing a project and pausing pickup, call the same function. - The tech-lead’s conversation is offered only what
actions.proposable()returns for the asker’s credential.
A card has one lifecycle and one door#
A card’s state changes only through lifecycle.card.transition() (ADR-0055). The door reads the card’s record and answers a repeated event from the row already recorded; asks the pure table whether the event is allowed from where the card is; runs the engine’s part of a person’s decision, if any; records the transition at the next sequence number unless another writer took it; and then applies what the table says follows.
- The tables in
lifecycle/table.pyare pure: no clock, no store, no tracker. The card states arebacklog,todo,running,waiting_on_a_person,merged,staged,delivered,closedandremoved. - A guard test allows no card writer outside the door; the only exception, by rule, is the box writing its progress marks.
- ADR-0055 is implemented although its record still reads Proposed. One event,
picked_up, is still undecided and refused from every state. - Jobs have no equivalent table. Job transitions are decided in the runner and the workflow, which is the part of ADR-0033 that remains proposed.
Refusals name the cause and the remedy#
A refusal says what failed and what to do. On the command line, cli_refusals.py matches a provider error to a known cause and prints a sentence with the cause and its remedy, followed by the provider’s own words. An error it does not recognise keeps the provider’s words and no remedy is guessed. Tests hold each cause to its own remedy and keep the core’s own bugs from being presented as a provider’s.
“Could not see” is not “nothing there”#
Every read on a tracker follows one rule, stated in its base class: None means the read failed; an empty list means the read succeeded and found nothing. Boards follow the same rule, and the adapter conformance suite checks it for trackers. Typed values such as Unreadable carry the same distinction elsewhere.
Refuse before writing#
A check that can refuse runs before anything is written. A refused card transition changes nothing and says why; the floor refuses a job before the implementation pass; the knowledge pipeline refuses a target outside its directory before anything is cloned; a refused command-line flag says that nothing was read and nothing was written.
One prompt budget that names what it leaves out#
A job’s prompt has one budget (ADR-0056): BEEVERFORGE_PROMPT_BUDGET, default 160,000 bytes, shared by the card, the plan and the documents the project declares. off turns it off; a value that cannot be read is refused. Every document left out is named in the prompt, by the path the agent can open and its size, under a heading that tells the agent to open it before changing what it governs. A document cut at its own length limit is listed with how much of it was inlined.
Vendor neutrality, enforced by tests#
tests/test_kernel_names_no_vendor.pyparsesbeeverforge/contractsandbeeverforge/policyand fails on any vendor name.tests/test_the_core_names_no_vendor.pyfails on a chat vendor named outside comments in the core.tests/test_the_core_addon_ledger.pyderives every import of a vendor SDK and holds the list to the ledger indocs/core/07-extensibility.md.tests/test_the_core_is_a_core.pyloads the core in a subprocess where every vendor SDK refuses to import, and checks that the action catalogue resolves and the panel starts.
The knowledge layer#
The knowledge layer is a module map of each repository, generated deterministically from directory structure, Python syntax trees and pattern matching over TypeScript, JavaScript and C# sources, with no LLM calls. The same repository state produces byte-identical output.
- It is on by default (
knowledge_map: true; ADR-0035) and can be turned off per project. - It is published to the project’s context repository under
.okf/repos/<owner>--<name>/, never to the repository being worked on. Publishing requiresproduct.docs_repo; without it the refresh does nothing. - An agent receives the map only when it is proven fresh against the agent’s own checkout. It speeds up finding code and does not replace reading it.
The product role#
The product role is opt-in. It is enabled by a product: section in the project’s registry entry, which must name the documentation repository that holds the product’s requirements (ADR-0019).
Sources#
docs/core/00-vision.mddocs/architecture.mdbeeverforge/actions/__init__.pybeeverforge/api/app.pybeeverforge/lifecycle/card.pybeeverforge/lifecycle/table.pybeeverforge/cli_refusals.pybeeverforge/adapters/tracker/base.pybeeverforge/loader.pybeeverforge/credentials.pybeeverforge/orchestrator/context.pybeeverforge/adapters/agent/base.pybeeverforge/knowledge/generator.pybeeverforge/knowledge/pipeline.pybeeverforge/contracts/product.pytests/test_the_card_lifecycle_has_one_door.pytests/test_kernel_names_no_vendor.pytests/test_the_core_is_a_core.py- ADR-0001, ADR-0019, ADR-0033, ADR-0035, ADR-0039, ADR-0055, ADR-0056