-
Notifications
You must be signed in to change notification settings - Fork 1
feat: registration domain refactor + Bedrock & Vertex runtime adapters #14
Description
Context
Issue #13 implements agentspec register as a thin CLI wrapper around POST /api/v1/register. That issue deliberately scopes out architectural changes to the control plane. This issue addresses the next layer: refactoring the registration flow so it can validate against real runtimes (Bedrock, Vertex, K8s) before issuing a token.
Prerequisite: #13 must be merged first.
Problem
packages/control-plane/api/register.py is a single FastAPI route handler that mixes HTTP request parsing, DB access, and token issuance in one place. There is no hook invoked on registration, so:
AgentObservationCRs are never created at register time — only on the first heartbeat- There is no path to validate an agent against AWS Bedrock or GCP Vertex before issuing a token
- The registration logic cannot be reused from the CLI without going through HTTP
Proposed Solution
Split the registration flow into a domain core that contains all business logic, and ports (interfaces) that the domain depends on for storage, token issuance, and runtime notification. Concrete adapters implement those ports for each target.
flowchart LR
CLI((CLI)) --> Domain
HTTP((HTTP)) --> Domain
subgraph Domain ["Registration Domain"]
direction TB
Main["register_agent(name, runtime, manifest)"]
Steps["• validate name (RFC 1123)<br>• invoke RuntimeNotifierPort<br>• store agent record<br>• issue JWT"]
Main --- Steps
end
Domain --> ASP[AgentStorePort]
Domain --> TIP[TokenIssuerPort]
Domain --> RNP[RuntimeNotifierPort]
classDef domainBox fill:#f9f9f9,stroke:#333,stroke-width:2px;
class Domain domainBox;
Ports (Python ABCs)
| Port | Methods |
|---|---|
AgentStorePort |
save(agent), find_by_name(name) |
TokenIssuerPort |
issue(agent_id) → (token, jti) |
RuntimeNotifierPort |
on_register(agent) → None |
Adapters
| Adapter | Runtime | Behavior |
|---|---|---|
PostgresStoreAdapter |
all | SQLAlchemy async upsert — wraps existing db/models.py |
JwtIssuerAdapter |
all | Wraps existing auth/keys.py — no change to token format |
K8sNotifierAdapter |
k8s |
Calls upsert_agent_observation() from k8s/upsert.py at register time, not just on heartbeat |
BedrockNotifierAdapter |
bedrock |
Calls AWS Bedrock Agents API to validate agent exists; raises RuntimeValidationError if not found |
VertexNotifierAdapter |
vertex |
Calls GCP Vertex AI Agents API to validate agent exists; raises RuntimeValidationError if not found |
NoopNotifierAdapter |
local, docker |
Pass-through — no external validation |
Note on bedrock and vertex runtimes
These extend the runtime enum in RegisterRequest (currently docker | local | k8s). Validation is registration-only — heartbeat protocol, CR structure, and operator reconciliation are unchanged.
Acceptance Criteria
Domain
-
register_agent()indomain/registration.pyhas zero HTTP, DB, or K8s imports - All three ports are Python ABCs in
ports/ - Domain unit-tested with in-memory mock adapters — no real DB or HTTP
- Key rotation (idempotent re-registration) preserved in domain logic
Adapters
-
K8sNotifierAdapter— CR created at register time, not only on heartbeat -
BedrockNotifierAdapter— strict:RuntimeValidationError(400)if agent not found -
VertexNotifierAdapter— strict:RuntimeValidationError(400)if agent not found -
NoopNotifierAdapter— no side effects forlocalanddocker
HTTP layer
-
api/register.pybecomes a thin adapter: parse → callregister_agent()→ return response - Existing
POST /api/v1/registerbehavior unchanged (same response schema, idempotent rotation) - All existing control-plane tests pass (
pytest tests/ -v)
CLI integration (depends on #13)
-
agentspec register agent.yaml --runtime bedrocktriggers Bedrock validation before token issuance -
agentspec register agent.yaml --runtime vertextriggers Vertex validation before token issuance - Clear error on failure:
✗ Runtime validation failed: Bedrock agent 'my-agent' not found in account
Files Changed
| File | Action |
|---|---|
packages/control-plane/domain/registration.py |
Create — domain core, no I/O imports |
packages/control-plane/ports/agent_store.py |
Create — AgentStorePort ABC |
packages/control-plane/ports/token_issuer.py |
Create — TokenIssuerPort ABC |
packages/control-plane/ports/runtime_notifier.py |
Create — RuntimeNotifierPort ABC + RuntimeValidationError |
packages/control-plane/adapters/postgres_store.py |
Create — wraps db/models.py |
packages/control-plane/adapters/jwt_issuer.py |
Create — wraps auth/keys.py |
packages/control-plane/adapters/k8s_notifier.py |
Create — wraps k8s/upsert.py |
packages/control-plane/adapters/bedrock_notifier.py |
Create |
packages/control-plane/adapters/vertex_notifier.py |
Create |
packages/control-plane/adapters/noop_notifier.py |
Create |
packages/control-plane/api/register.py |
Refactor — thin HTTP adapter |
packages/control-plane/schemas.py |
Edit — extend runtime enum with bedrock, vertex |
packages/control-plane/tests/test_registration_domain.py |
Create — unit tests with mock adapters |
Unchanged: k8s/upsert.py, auth/keys.py, db/models.py, db/base.py
Out of Scope
- Python SDK
start_push_mode()changes - Operator Helm chart changes
- JWT format changes
- K8s CR schema changes
- Bedrock/Vertex heartbeat handling