Skip to content

Latest commit

 

History

History
202 lines (175 loc) · 8.37 KB

File metadata and controls

202 lines (175 loc) · 8.37 KB

Component Design

A Component is any registered unit of functionality in Brain and must be one of: Resource (L0), Service (L1), or Actor (L2). All Components must self-register by calling register_component() from lib/shared/manifest.py.

Check the Glossary for key terms such as Component, Manifest, Resource, Service, et cetera.


Component Rules (Global)

Required semantics

  • Every Component has a globally unique ComponentId.
  • Every Component declares layer, system, and one or more module_roots.
  • Every L0 Resource and L1 Service exports a health() contract.
  • ComponentId is schema-safe (^[a-z][a-z0-9_]{1,62}$).
  • Registration is global and process-local via register_component(...).
  • Registry is the source of truth for identity and ownership validation.

Health contract rule

  • Services and Resources must expose health().
  • Each Component may apply its own internal timeout semantics.
  • Core aggregate health enforces a global max timeout from core.health.max_timeout_seconds; any health() call exceeding that limit is unhealthy by definition.

Registry behavior

  • One global registry contains all Component types.
  • list_components() is the canonical complete view.
  • Ownership checks are enforced for L0/L1 relationships:
    • on registration (non-strict owner existence; import-order tolerant)
    • on assert_valid() (strict owner existence)

L0 Resource Design

An L0 Resource is Brain's interface to infrastructure with durable or real-world side effects. Each Resource governs one underlying Provider surface.

Model

  • Declared via ResourceManifest.
  • Required:
    • id: ComponentId
    • layer = 0
    • kind in {"substrate", "adapter"}
    • module_roots
  • Optional:
    • owner_service_id (required in practice for owned Resources)

Architectural expectations

  • L0 access is gated by owning L1 Service(s), never by L2 directly.
  • Resource ownership must be explicit and unambiguous.
  • Resource contracts should remain implementation-agnostic with respect to the underlying Provider where practical.
  • If owner_service_id is set, it must resolve to a registered L1 Service.
  • Resource IDs must match what owning Services declare in owns_resources.

Implementation expectations

  • Package should export a top-level MANIFEST constant that calls register_component(ResourceManifest(...)).
  • Resource modules contain Substrate/Adapter implementation, not business policy.

L1 Service Design

An L1 Service is Brain business logic with authoritative public contracts.

Model

Declared via ServiceManifest. Required:

  • id: ComponentId
  • layer = 1
  • system in {"state", "action", "control"}
  • module_roots
  • public_api_roots
  • owns_resources: FrozenSet[ComponentId]

Architectural expectations

  • Services may call other Services only through their Public APIs.
  • Services may not import other Services' internal implementations.
  • Services gate all L0 access and enforce domain invariants/policy.
  • Service ID is canonical for schema naming (schema_name == ComponentId).
  • For PostgreSQL, which is a shared Substrate:
    • each Service owns exactly its schema
    • no cross-schema direct access
    • no cross-Service foreign keys
    • this means you have to do joins and RI in code; deal with it

Implementation expectations

  • Service package should export MANIFEST = register_component(ServiceManifest(...)).
  • owns_resources must list L0 Component IDs it owns.
  • If a Resource declares owner_service_id, it must match the owning Service id.
  • Public API methods exposed must be decorated with lib.shared.logging.public_api_instrumented(...) so invocation observability concerns (logging, metrics, tracing) remain consistent and composable across Services.
  • Typed contracts (settings, envelopes, request/response models, structured errors) should follow the Pydantic contract rules in Conventions.
  • Service settings key definitions and override behavior should align with Configuration Reference.

Optional Boot Hook Contract

Any Component may define an optional boot.py module for startup orchestration. This is for cross-Component runtime coordination, not for primary configuration loading.

Ordering

  • Core startup resolves configuration first.
  • Boot hook orchestration runs after configuration is available.
  • If any configured Component hook fails under configured retry/timeout policy, Core startup fails hard and exits with error.

Required symbols (when boot.py exists)

  • dependencies: tuple[str, ...]
  • is_ready(ctx: BootContext) -> bool
  • boot(ctx: BootContext) -> None

Semantics

  • dependencies contains ComponentId values for required upstream Components.
  • is_ready(...) must be non-blocking and return readiness truthfully.
  • boot(...) performs one-time startup work and must raise on failure.
  • Hooks receive runtime dependencies/settings via BootContext; Components should not rely on mutable module globals for boot state.

Optional After-Boot Hook Contract

Any Component may define an optional after_boot(...) function in its component.py module for post-boot initialization that must run after all boot hooks succeed.

Ordering

  • after_boot(...) runs after global boot orchestration completes.
  • after_boot(...) runs before the Core HTTP runtime starts serving.
  • If any after_boot(...) hook raises, Core startup fails hard and exits with error.

Contract

  • Signature: after_boot(*, settings: BrainSettings, components: Mapping[str, object]) -> None
  • settings is fully resolved typed runtime configuration.
  • components is the instantiated component map keyed by ComponentId.

Semantics

  • Use this hook for post-boot initialization that requires a fully booted runtime graph.
  • This hook is not a readiness gate and does not participate in boot retry/timeout policy.
  • Hooks must be deterministic and raise on failure.

Capability Package Design

Capability packages are immutable runtime contracts owned by Capability Engine.

Package shape

  • Root location is capabilities/.
  • Capability package discovery is recursive under that root.
  • Intermediate directories may be used for grouping only.
  • Each package directory is self-named in kebab-case and must exactly match capability_id.
  • Required files in every package:
    • capability.json
    • README.md
  • Op package:
    • manifest-only wrapper over primitive call target
    • no required Python module
  • Logic Skill package:
    • execute.py entrypoint module
    • test/ with at least one test_*.py file
  • Pipeline Skill package:
    • declarative pipeline list of steps
    • each step is either a capability ID string or an object with capability plus optional input_mapping
    • no required Python module

Manifest invariants

  • Manifest schema is immutable at runtime.
  • capability_id identifies the package and is the runtime invoke target.
  • Manifest version is semver and is audit metadata, not a runtime selector.
  • Exactly one runtime manifest version is active per capability_id.
  • Registry validation is fail-closed at boot:
    • invalid schema fails boot
    • duplicate capability_id fails boot, even across different grouping paths
    • unknown dependency or pipeline member fails boot
  • required_capabilities is allowed only on logic skills; other capability kinds must omit it.
  • Runtime overlays may not mutate capability manifests.

Practical Registration Pattern

Each Component package should self-register at import time with a single exported MANIFEST symbol:

  • Service example: services/state/<service>/__init__.py
  • Resource example: resources/substrates/<resource>/__init__.py

This enables deterministic pre-flight checks and bootstrap orchestration from the global registry.


End of Component Design