Arquitectura del núcleo de BeeverForge

Principios de ingeniería

Los principios del núcleo están escritos en su documento de visión y en sus registros de decisión. Esta página los enumera con el código que hace cumplir cada uno, e indica dónde el código no llega a lo que dice el enunciado.

Principios de la visión#

De docs/core/00-vision.md. Son objetivos declarados; cuando el código no cumple uno por completo, el elemento indica hasta dónde llega.

  1. El núcleo no nombra a ningún fabricante. A un fabricante se llega mediante un puerto, elegido por un registro, a partir de la configuración. La prueba que lo hace cumplir solo cubre beeverforge/contracts y beeverforge/policy; las filas de adaptadores incluidos nombran a sus fabricantes, y el destino de métricas se infiere como dynamodb cuando BEEVERFORGE_METRICS_TABLE está definida.
  2. Todo lo que está fuera del núcleo es reemplazable, por alguien que no sean los mantenedores: un punto de entrada, no un pull request. El motor es la excepción: Temporal no está detrás de un puerto.
  3. Una aplicación construida sobre el núcleo no es el núcleo. Usa la misma superficie que usaría la aplicación de un tercero.
  4. El núcleo es útil por sí mismo. El documento añade que nada de lo que incluye se retira para fabricar una vía de actualización; es un compromiso declarado, que el código no puede demostrar.
  5. Un contrato se deriva de lo que el núcleo llama, nunca de lo que ofrece un proveedor.
  6. Un eje es agnóstico cuando nace con dos implementaciones, no cuando declara una interfaz (ADR-0022). Varios ejes todavía no lo cumplen; Modelo de extensión los enumera.

Decisiones de ADR-0001#

ADR-0001 fija el modelo conceptual. Sus decisiones, por encabezado:

  • D-1 El framework y el proyecto se separan mediante un manifiesto declarativo.
  • D-2 El conocimiento del proyecto es una cascada de autoridad: endurecer, nunca relajar.
  • D-3 El framework es dueño de los espacios; la conformidad los hace cumplir.
  • D-4 Un entorno efímero con una caché de dependencias persistente.
  • D-5 Un worker genérico por ticket; el ejecutor y el revisor son contextos separados.
  • D-6 La etiqueta es una suposición; el diff es la verdad. Los componentes resuelven los stacks.
  • D-7 Las dependencias declaradas son la verdad; las inferidas son orientativas.
  • D-8 Un control de calidad de la especificación: confiar para empezar, verificar para terminar.
  • D-9 La documentación tiene roles estándar, no ubicaciones estándar; los ADR son la constitución.
  • D-10 Derivar, no mantener.
  • D-11 Determinista primero.
  • D-12 La plataforma es un operador del ciclo de vida, no alguien que solo abre pull requests.
  • D-13 Los evaluadores son llamadas estructuradas sobre controles deterministas; el comportamiento del agente se mide, no se supone.
  • D-14 Un solo inquilino, varios proyectos.
  • D-15 Recogida, propiedad y gestión de impedimentos.
  • D-16 Temporal como motor de ejecución durable.
  • D-17 Una caja de tareas remota que ejecuta el job completo. En este repositorio esa caja es solo una descripción; su ejecutor es un complemento.

Invariantes, comprobados contra el código#

Nunca un bloqueo silencioso
Toda espera en el workflow del job tiene un plazo o una persona al final: la vigilancia del merge, la aprobación de producción y un impedimento sin respuesta tienen plazos, y una espera por decisión se mantiene hasta que se responde. Las esperas incluyen las opciones que una persona puede tomar. Un pull request en el que ningún check informa se señala, no se interpreta como verde.
Nada se pierde, nada se duplica
Los jobs son workflows durables. Los pull requests son buscar y luego actualizar; antes de reconstruir una rama, el ejecutor pregunta a la plataforma Git si existe un pull request, y si no puede leer la respuesta se detiene en lugar de suponer.
El cupo se libera con el merge
El workflow del job conserva su cupo hasta que el pull request recibe el merge, hasta que termina la promoción cuando está habilitada, o hasta que el job se omite. Un job en espera conserva su cupo: un impedimento simple se omite tras 3 días, y una espera por decisión aguarda hasta que se responde.
La calidad es un piso
Los roles test y security son obligatorios antes de la pasada de implementación. El piso comprueba que los roles existen, no lo que ejecutan, y el control security heredado es no bloqueante.
El código es la fuente de verdad
El mapa de conocimiento se entrega a un agente solo cuando sus sumas de comprobación prueban que describe la copia de trabajo de ese mismo job; si no, no se entrega nada.
Atribuido al bot
La fábrica hace commits y actúa con la identidad de bot que define el despliegue (BEEVERFORGE_BOT_NAME, BEEVERFORGE_BOT_EMAIL).
Mínimo privilegio
En parte. El campo permissions del manifiesto se acepta y está marcado como inerte: ningún código lo lee, y el endurecimiento de permisos por proyecto no está implementado. Lo que limita una ejecución es el aislamiento de la caja y su box.env.

Una capa de acciones para todos los transportes#

Todo lo que una persona puede pedir a la fábrica está escrito una sola vez, en el catálogo de acciones (ADR-0039): beeverforge/actions/, ejecutado mediante actions.perform. Quién pidió es un parámetro de la llamada, no una propiedad del transporte.

  • La API del panel sirve el catálogo en POST /api/act/{name}, y sus rutas con nombre llaman a la misma función.
  • La línea de comandos lo sirve como beeverforge act, y varios de sus otros comandos, como añadir o quitar un proyecto y pausar la recogida, llaman a la misma función.
  • A la conversación del tech-lead solo se le ofrece lo que actions.proposable() devuelve para la credencial de quien pregunta.

Una tarjeta tiene un ciclo de vida y una puerta#

El estado de una tarjeta solo cambia mediante lifecycle.card.transition() (ADR-0055). La puerta lee el registro de la tarjeta y responde a un evento repetido con la fila ya registrada; pregunta a la tabla pura si el evento está permitido desde donde está la tarjeta; ejecuta la parte del motor de la decisión de una persona, si la hay; registra la transición con el siguiente número de secuencia salvo que otro escritor lo haya tomado; y después aplica lo que la tabla dice que sigue.

  • Las tablas de lifecycle/table.py son puras: sin reloj, sin almacén, sin tracker. Los estados de la tarjeta son backlog, todo, running, waiting_on_a_person, merged, staged, delivered, closed y removed.
  • Una prueba de guarda no permite ningún escritor de tarjetas fuera de la puerta; la única excepción, por regla, es la caja escribiendo sus marcas de progreso.
  • ADR-0055 está implementado aunque su registro todavía dice Proposed. Un evento, picked_up, sigue sin decidirse y se rechaza desde cualquier estado.
  • Los jobs no tienen una tabla equivalente. Las transiciones de los jobs se deciden en el ejecutor y en el workflow, que es la parte de ADR-0033 que sigue propuesta.

Los rechazos nombran la causa y la solución#

Un rechazo dice qué falló y qué hacer. En la línea de comandos, cli_refusals.py relaciona un error del proveedor con una causa conocida e imprime una frase con la causa y su solución, seguida de las palabras del propio proveedor. Un error que no reconoce conserva las palabras del proveedor y no se inventa ninguna solución. Las pruebas exigen a cada causa su propia solución y evitan que los fallos del propio núcleo se presenten como fallos del proveedor.

«No se pudo ver» no es «no hay nada»#

Toda lectura de un tracker sigue una regla, enunciada en su clase base: None significa que la lectura falló; una lista vacía significa que la lectura tuvo éxito y no encontró nada. Los tableros siguen la misma regla, y la suite de conformidad de adaptadores la comprueba para los trackers. Valores tipados como Unreadable mantienen la misma distinción en otras partes.

Rechazar antes de escribir#

Una comprobación que puede rechazar se ejecuta antes de escribir nada. Una transición de tarjeta rechazada no cambia nada y explica por qué; el piso rechaza un job antes de la pasada de implementación; el pipeline de conocimiento rechaza un destino fuera de su directorio antes de clonar nada; una opción de línea de comandos rechazada indica que no se leyó ni se escribió nada.

Un presupuesto de prompt que nombra lo que deja fuera#

El prompt de un job tiene un único presupuesto (ADR-0056): BEEVERFORGE_PROMPT_BUDGET, predeterminado 160.000 bytes, compartido por la tarjeta, el plan y los documentos que declara el proyecto. off lo desactiva; un valor que no puede leerse se rechaza. Cada documento que queda fuera se nombra en el prompt, con la ruta que el agente puede abrir y su tamaño, bajo un encabezado que indica al agente que lo abra antes de cambiar lo que regula. Un documento recortado por su propio límite de longitud se lista con cuánto de él se incluyó.

Neutralidad respecto a fabricantes, impuesta por pruebas#

  • tests/test_kernel_names_no_vendor.py analiza beeverforge/contracts y beeverforge/policy y falla ante cualquier nombre de fabricante.
  • tests/test_the_core_names_no_vendor.py falla si se nombra un fabricante de chat fuera de los comentarios del núcleo.
  • tests/test_the_core_addon_ledger.py deriva cada importación de un SDK de fabricante y mantiene la lista igual al registro de docs/core/07-extensibility.md.
  • tests/test_the_core_is_a_core.py carga el núcleo en un subproceso donde todo SDK de fabricante se niega a importarse, y comprueba que el catálogo de acciones se resuelve y que el panel arranca.

La capa de conocimiento#

La capa de conocimiento es un mapa de módulos de cada repositorio, generado de forma determinista a partir de la estructura de directorios, los árboles sintácticos de Python y la búsqueda de patrones en fuentes TypeScript, JavaScript y C#, sin llamadas a un LLM. El mismo estado del repositorio produce una salida idéntica byte a byte.

  • Está activa de forma predeterminada (knowledge_map: true; ADR-0035) y puede desactivarse por proyecto.
  • Se publica en el repositorio de contexto del proyecto bajo .okf/repos/<owner>--<name>/, nunca en el repositorio en el que se trabaja. Publicar requiere product.docs_repo; sin él, la actualización no hace nada.
  • Un agente recibe el mapa solo cuando se ha probado que está actualizado respecto a su propia copia de trabajo. Acelera encontrar código y no sustituye leerlo.

El rol de producto#

El rol de producto es opcional. Se habilita con una sección product: en la entrada del proyecto en el registro, que debe nombrar el repositorio de documentación que contiene los requisitos del producto (ADR-0019).

Fuentes#