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.

  1. 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/contracts and beeverforge/policy only; the built-in adapter rows name their vendors, and the metrics sink is inferred as dynamodb when BEEVERFORGE_METRICS_TABLE is set.
  2. 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.
  3. An application built on the core is not the core. It uses the same surface a third party’s application would.
  4. 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.
  5. A contract is derived from what the core calls, never from what a provider offers.
  6. 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 test and security roles are required before the implementation pass. The floor checks that the roles exist, not what they run, and the inherited security gate 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 permissions field is accepted and marked inert: no code reads it, and per-project permission tightening is not implemented. The box’s isolation and its box.env are 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.py are pure: no clock, no store, no tracker. The card states are backlog, todo, running, waiting_on_a_person, merged, staged, delivered, closed and removed.
  • 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.py parses beeverforge/contracts and beeverforge/policy and fails on any vendor name.
  • tests/test_the_core_names_no_vendor.py fails on a chat vendor named outside comments in the core.
  • tests/test_the_core_addon_ledger.py derives every import of a vendor SDK and holds the list to the ledger in docs/core/07-extensibility.md.
  • tests/test_the_core_is_a_core.py loads 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 requires product.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#