BeeverForge core architecture
Extension model
Every external dependency of the core except the engine, Temporal, sits behind an adapter, and every adapter is chosen by a registry from configuration. A new provider is added by installing a package that declares an entry point, without changing the core.
Axes#
An axis is one kind of thing the core can be extended with. The published list is AXES in beeverforge/plugins.py:
tracker board forge ci harness box event metrics channel
notifier identity role session_store token_pool box_runner
credential board_setup extract embed preview credential_store
A test holds this list to exactly what the registries ask for, and another checks that every registry consults the entry-point loader.
| Axis | Built-in kinds |
|---|---|
harness | claude_code, codex, kimi, opencode |
tracker | local, github, jira, azure_devops |
board | local, github, jira, azure_devops |
forge | local, github, azure_devops |
ci | none and local, github and github_actions, azure_devops and azure_pipelines; each pair names one observer |
box | container, worktree, fargate (description only; its runner is an add-on) |
box_runner | None |
channel | panel |
notifier | panel |
identity | local, oidc |
metrics | null, sqlite, memory |
preview | compose, none |
credential_store | env, file |
The entry-point group#
- A package declares its adapters in one entry-point group,
beeverforge.adapters. Each name is<axis>.<kind>, for exampleforge.gitlab. A name of any other shape is skipped with a warning that shows the expected form. - Lazy. Registries look in their own table first. Entry points are loaded once, on the first lookup of a kind that is not built in, and cached; a deployment that uses only built-in kinds never loads one. Listing installed add-ons reads package metadata without importing them.
- A broken add-on is logged and ignored; the others still load.
- Built-ins win a collision. Declaring
forge.githubdoes not replace the shipped row: the built-in answers and the collision is logged. An add-on cannot change what an existing kind means for every project on a deployment. - An unknown kind refuses by name. The registry raises with the kind, the known kinds and, where the kind belongs to a known add-on package, which package ships it. It never falls back to a default provider.
Born with two#
ADR-0022 sets the bar: an axis is agnostic not because it declares an interface, but when it is born with more than one implementation and a seam that dispatches between them. Protocols are @runtime_checkable and tests check them with isinstance.
The rule is not met on every axis today. The channel, notifier, session_store and token_pool axes have one built-in kind each, and box_runner has none. No test requires two built-ins per axis; per-axis tests exist for the preview and credential_store axes.
Adapter conformance#
beeverforge conformance-adapter <kind> <factory> runs an adapter against the platform’s conformance suite, beeverforge/conformance/adapters.py. The factory is an import path, pkg.module:attr, naming an instance, a class or a function that returns one.
- The checks are plain functions that return findings. They construct nothing remote and change nothing remote; they exercise the local contract: signatures, degradation rules and the shape of refusals.
- There are checks for channels, notifiers, identity, boards, trackers, forges, harnesses, CI observers, boxes and previews.
- Examples: a tracker read must return
Nonewhen it could not read and an empty list when there is nothing; a forge must send a credential only to the host that issued it; an observer’s health check returns a boolean and never raises.
beeverforge conformance <project> is a different command: it checks a project’s manifest, not an adapter.
The stack needs no adapter#
The core names no build tool. The manifest’s setup: and validate: entries are shell commands, and the box runs each one as written: sh -c in a container, a shell in a worktree. A project in any language is supported by declaring its own commands, with no adapter.
Example: a notifier add-on#
From docs/writing-an-addon.md. The package declares one entry point:
[project.entry-points."beeverforge.adapters"]
"notifier.acme" = "beeverforge_acme:build_notifier"
and implements what the core calls:
class AcmeNotifier:
def notify(self, *, message: str, level: str = "info",
about: str = "") -> str | None:
... # your provider
return None # or the provider's own handle for the message
def build_notifier(project=None, **_kw):
return AcmeNotifier()
Each axis calls its builder with its own arguments; the guide lists them per axis, and covers how a row declares its display name and the remedies the core shows for that provider.
Sources#
beeverforge/plugins.pybeeverforge/adapters/forge/registry.pybeeverforge/adapters/agent/registry.pybeeverforge/adapters/sandbox/registry.pybeeverforge/adapters/environment/registry.pybeeverforge/conformance/adapters.pybeeverforge/cli.pybeeverforge/orchestrator/validation.pydocs/writing-an-addon.mddocs/core/07-extensibility.mdtests/test_a_stranger_can_add_an_adapter.py- ADR-0022, ADR-0034