This document defines how Components communicate, behave, and enforce rules within Brain. For the structural model of Layers, Systems, and boundaries, see Boundaries & Responsibilities.
Check the Glossary for key terms such as Layer, System, Resource, Service, et cetera.
TL;DR — services/*/*/component.py exports a list of interfaces which are
the canonical surface area for a given Service.
Each Service must define a Public API. These Public APIs:
- Are the canonical and authoritative (Python) interface to the Service
- Are the sole permitted surface for other Component callers
- Define method signatures using envelope-like request/response messages
The Public APIs are canonical. L2 access is provided by a small published HTTP
surface implemented in api.py modules as FastAPI route registrars, while
internal East-West traffic among Services uses the in-process Python Public
API exclusively.
The Brain Core SDK is therefore a thin HTTP client layered on top of the published subset of Service Public APIs. It currently talks to Brain Core over a Unix domain socket and only exposes a limited subset of operations.
The Public API and published HTTP surface are not required to be one-to-one. A Service may expose Public API methods that are internal-only and therefore not published on the Brain Core SDK.
Pydantic is the canonical contract system for configuration and typed data exchange in Brain.
- Use
BaseSettings/pydantic-settingsfor runtime configuration models. - Use
BaseModelfor cross-Service and cross-Layer boundary contracts. - Use
BaseModelfor shared structured error and envelope primitives.
- Set
extra="forbid"unless there is a concrete, documented reason not to. - Set
frozen=Trueunless the model is explicitly stateful and mutable by design.
- Validate at ingress boundaries using
model_validate(...). - Treat validated models as authoritative inside domain logic; avoid re-validating ad hoc dictionaries throughout call chains.
- Prefer explicit validation failures with stable error messages over silent coercion paths.
- Use
model_dump(mode="python")for in-process structured handoff where a plain mapping is required. - Keep transport adapters explicit about mapping to/from wire types.
- Do not maintain parallel contract shapes for the same boundary (for example: dataclass + dict + Pydantic for one message type). One boundary contract, one canonical typed model.
All cross-Layer and cross-Service communication must use Envelopes. An Envelope consists of:
- Metadata
- Payload
- Errors
Published HTTP routes should generally use operation-specific request/response
models that remain envelope-like (metadata, typed payload, and errors
where applicable), rather than a single polymorphic wire envelope for every
endpoint.
Command Envelope: Generated by L2 for Capability invocation. Event Envelope: Generated by L1 upon external or system-triggered activity. Result Envelope: Used for responses to other Enveloped messages. Stream Envelope: [TODO]/reserved - future real-time streaming support.
envelope_id: required ULIDtrace_id: required ULIDparent_id: optional ULIDtimestamp: required int64kind: required string (one ofcommand,event,result,stream)source: required string (e.g.cli,agent,switchboard,job)principal: required string (e.g.operator,commitment,core)
Envelope subclasses may append their own metadata. For clarity, source is
the immediate emitting Component for "this" specific Envelope, whereas
principal is the accountable identity (effective authority) for the request.
Components are required to propagate principal unchanged across calls.
Illustrative (non-literal) example: The Operator requests a reminder in 1 hour. A message is passed from the Switchboard to the Agent like:
source = "switchboard"principal = "operator"which results in a message from the Agent to the Scheduler like:source = "agent"principal = "operator"
An hour later, the schedule fires and the Job invokes the Agent like:
source = "job"principal = "operator"which results in a message from the Agent to the Attention Router like:source = "agent"principal = "operator"
A trace_id scopes a single execution episode. In the example above, the first
two Envelopes share the same trace_id. The third and fourth share a new
trace_id (distinct from the first two), and the third sets parent_id to the
envelope_id of the scheduling Envelope from the prior Trace.
This keeps each execution episode independently observable while preserving cross-Trace causality for long-term lineage and analysis. This provides for a DAG of Envelopes across time, with Trace segments as execution partitions.
Domain-specific content.
You can use these oats to make oatmeal, bread, whatever you want. I don't care, they're your oats.
— Dwight K. Schrute
Most usually Errors will be present in a Result Envelope, but are not invalid in any Envelope. Errors are a collection of structured objects representing some failure mode/state.
The Principal is "who the system treats as accountable" for a given request.
operator - All "personal assistant" work should ultimately roll up to the
Operator.
<service> (e.g. switchboard, ctlc) - This represents a Layer 1
Service acting autonomously. This is used when Services initiate work
without an immediate upstream request (think scheduled jobs, inbound interrupt,
etc.)
core - Rare. Only used for truly low-level, cross-cutting "infrastructure"
behavior which are explicitly system-meta in nature.
The Brain Core SDK is the public interface for L2 Actors of Brain Core using the published HTTP surface. All L2 Actors must be built on the Brain Core SDK.
The SDK contains no business logic; it simply exists as an access layer across process boundaries to the published subset of L1 Service Public APIs.
The Capability SDK supports registration and management of Capabilities (Ops and Skills) including both logic and metadata such as Policy declarations.
Errors must be categorized as:
- terminal
- retriable
Errors may carry optional category fields such as:
- conflict
- dependency
- not_found
- policy
- validation
Policy violations are, by definition, terminal, and should not be retried.
To avoid ambiguity at Service boundaries:
- Domain failures (validation, conflict, not_found, policy) are returned as typed structured errors in the envelope-like response.
- Transport/infrastructure failures (dependency unavailable, internal runtime faults) are surfaced as transport failures at the HTTP boundary.
This keeps domain behavior explicit and machine-readable while preserving normal transport semantics for network/runtime outages.
All Capability invocations (including within Skills) MUST pass through
Capability Engine invoke(). Skills must not directly call other Skills or
Ops by importing implementations. Policy Service evaluation is mandatory and
recursive.
Capability Engine and Policy Service must persist structured lineage and policy audit metadata for every invocation (allowed or denied).
Required lineage fields:
envelope_idtrace_idparent_idinvocation_idparent_invocation_idsourceactorchannel
Required policy/audit fields:
capability_idcapability_versionpolicy_decision_idpolicy_regime_idallowed(bool)reason_codes[]obligations[]approval proposal tokenwhen approval is required
Nested capability calls must mint a new envelope_id, set parent_id to the
parent envelope, and route through Capability Engine public invoke API so the
same policy and audit contract is enforced recursively.
Policy Service -> Attention Router approval notifications must be token-only.
Required outbound notification fields:
proposal_tokencapability_idcapability_versionsummaryactorchanneltrace_idinvocation_idexpires_at
Attention Router -> Policy Service correlation payloads must include actor and channel plus one of the deterministic correlators:
approval_tokenreply_to_proposal_tokenreaction_to_proposal_token
message_text may be included for immediate next-turn correlation and
disambiguation fallback.
- L2 Actors are process-and-network isolated
- L1 Services are process-local, but restricted to Public APIs
- L0 Substrates and Adapters are non-local
End of Conventions