Skip to content

Implement AgentCapability CRD for service registration #126

@bussyjd

Description

@bussyjd

Summary

Define and implement the AgentCapability Custom Resource Definition (CRD) - a Kubernetes-native way to represent services that can be registered on-chain via ERC-8004.

Background

The orchestrator needs a Kubernetes-native mechanism to discover and track services deployed in the stack. Following established patterns (ServiceMonitor for Prometheus, Certificate for cert-manager), we'll use a CRD that:

  1. Networks emit when deployed (e.g., ethereum creates an RPC capability)
  2. The orchestrator controller watches and reconciles
  3. Drives ERC-8004 registration and HTTPRoute generation

See Orchestrator PRD for full context.

Requirements

Must Have

  • Define AgentCapability CRD schema (v1alpha1)
  • Deploy CRD to cluster during obol stack up
  • Support capability types: rpc, inference, fine-tune, validator
  • Include pricing specification (model, amount, asset, network)
  • Include endpoint specification (service, port, path)
  • Include publish flag to control visibility
  • Status subresource for registration state

Should Have

  • Validation webhooks for enum fields
  • Default values for common fields
  • Short name (acap) for kubectl

Nice to Have

  • Printer columns for kubectl get agentcapabilities
  • OpenAPI schema documentation

CRD Specification

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: agentcapabilities.obol.network
  annotations:
    controller-gen.kubebuilder.io/version: v0.14.0
spec:
  group: obol.network
  names:
    plural: agentcapabilities
    singular: agentcapability
    kind: AgentCapability
    shortNames:
      - acap
  scope: Namespaced
  versions:
    - name: v1alpha1
      served: true
      storage: true
      subresources:
        status: {}
      additionalPrinterColumns:
        - name: Type
          type: string
          jsonPath: .spec.type
        - name: Published
          type: boolean
          jsonPath: .spec.publish
        - name: Registered
          type: boolean
          jsonPath: .status.registered
        - name: Age
          type: date
          jsonPath: .metadata.creationTimestamp
      schema:
        openAPIV3Schema:
          type: object
          required:
            - spec
          properties:
            spec:
              type: object
              required:
                - type
                - endpoint
              properties:
                type:
                  type: string
                  description: "Type of capability being offered"
                  enum:
                    - rpc
                    - inference
                    - fine-tune
                    - validator
                protocol:
                  type: string
                  description: "Protocol identifier (e.g., ethereum-json-rpc, openai-chat)"
                chain:
                  type: object
                  description: "Blockchain network details (for rpc type)"
                  properties:
                    id:
                      type: integer
                      description: "Chain ID (e.g., 1 for mainnet)"
                    name:
                      type: string
                      description: "Human-readable network name"
                pricing:
                  type: object
                  description: "Payment requirements for this capability"
                  properties:
                    model:
                      type: string
                      description: "Pricing model"
                      enum:
                        - per-request
                        - per-token
                        - per-epoch
                        - per-hour
                        - free
                      default: per-request
                    amount:
                      type: string
                      description: "Price amount in asset units (e.g., '0.0001')"
                      default: "0"
                    asset:
                      type: string
                      description: "Payment asset symbol"
                      default: "USDC"
                    network:
                      type: string
                      description: "Payment network (e.g., base, base-sepolia)"
                      default: "base-sepolia"
                endpoint:
                  type: object
                  required:
                    - service
                    - port
                  description: "Kubernetes service endpoint"
                  properties:
                    service:
                      type: string
                      description: "Kubernetes service name"
                    port:
                      type: integer
                      description: "Service port number"
                    path:
                      type: string
                      description: "URL path prefix"
                      default: "/"
                publish:
                  type: boolean
                  description: "Whether to publish this capability to ERC-8004"
                  default: true
            status:
              type: object
              properties:
                registered:
                  type: boolean
                  description: "Whether capability is registered on-chain"
                agentId:
                  type: integer
                  description: "ERC-8004 agent NFT ID"
                registryTxHash:
                  type: string
                  description: "Transaction hash of registration"
                tunnelPath:
                  type: string
                  description: "Public URL path via cloudflared"
                httpRouteName:
                  type: string
                  description: "Generated HTTPRoute resource name"
                lastSync:
                  type: string
                  format: date-time
                  description: "Last successful reconciliation"
                conditions:
                  type: array
                  items:
                    type: object
                    properties:
                      type:
                        type: string
                      status:
                        type: string
                      lastTransitionTime:
                        type: string
                        format: date-time
                      reason:
                        type: string
                      message:
                        type: string

Example Resources

Ethereum RPC Capability

apiVersion: obol.network/v1alpha1
kind: AgentCapability
metadata:
  name: execution-rpc
  namespace: ethereum-knowing-wahoo
spec:
  type: rpc
  protocol: ethereum-json-rpc
  chain:
    id: 1
    name: mainnet
  pricing:
    model: per-request
    amount: "0.0001"
    asset: USDC
    network: base-sepolia
  endpoint:
    service: ethereum-execution-reth-mainnet
    port: 8545
    path: /
  publish: true

Inference Capability

apiVersion: obol.network/v1alpha1
kind: AgentCapability
metadata:
  name: llama-inference
  namespace: unsloth
spec:
  type: inference
  protocol: openai-chat
  pricing:
    model: per-token
    amount: "0.00001"
    asset: USDC
    network: base-sepolia
  endpoint:
    service: unsloth-api
    port: 8000
    path: /v1/chat/completions
  publish: true

Fine-tuning Capability

apiVersion: obol.network/v1alpha1
kind: AgentCapability
metadata:
  name: unsloth-finetune
  namespace: unsloth
spec:
  type: fine-tune
  protocol: unsloth-v1
  pricing:
    model: per-epoch
    amount: "1.00"
    asset: USDC
    network: base-sepolia
  endpoint:
    service: unsloth-api
    port: 8000
    path: /v1/fine-tune
  publish: true

Implementation

Option A: Static YAML (MVP)

Add CRD manifest to internal/embed/infrastructure/:

internal/embed/infrastructure/
├── helmfile.yaml
├── crds/
│   └── agentcapability-crd.yaml  # NEW
└── ...

Deploy via raw chart in helmfile:

- name: obol-crds
  namespace: kube-system
  chart: bedag/raw
  values:
    - resources:
        - {{ readFile "crds/agentcapability-crd.yaml" | nindent 10 }}

Option B: Controller-gen (Production)

Use kubebuilder/controller-gen for type-safe Go types:

// internal/api/v1alpha1/agentcapability_types.go
package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
// +kubebuilder:printcolumn:name="Published",type=boolean,JSONPath=`.spec.publish`
// +kubebuilder:printcolumn:name="Registered",type=boolean,JSONPath=`.status.registered`

type AgentCapability struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   AgentCapabilitySpec   `json:"spec,omitempty"`
    Status AgentCapabilityStatus `json:"status,omitempty"`
}

type AgentCapabilitySpec struct {
    Type     CapabilityType `json:"type"`
    Protocol string         `json:"protocol,omitempty"`
    Chain    *ChainSpec     `json:"chain,omitempty"`
    Pricing  *PricingSpec   `json:"pricing,omitempty"`
    Endpoint EndpointSpec   `json:"endpoint"`
    Publish  bool           `json:"publish,omitempty"`
}

// +kubebuilder:validation:Enum=rpc;inference;fine-tune;validator
type CapabilityType string

// ... rest of types

Recommendation: Start with Option A for MVP, migrate to Option B when building the controller.

Files to Create/Modify

  • internal/embed/infrastructure/crds/agentcapability-crd.yaml - CRD manifest
  • internal/embed/infrastructure/helmfile.yaml - Add CRD deployment
  • internal/embed/networks/ethereum/helmfile.yaml.gotmpl - Emit AgentCapability on install
  • internal/embed/networks/helios/helmfile.yaml.gotmpl - Emit AgentCapability on install
  • internal/embed/networks/aztec/helmfile.yaml.gotmpl - Emit AgentCapability on install

Testing

# Deploy stack with CRD
obol stack up

# Verify CRD registered
obol kubectl get crd agentcapabilities.obol.network

# Install network (should create AgentCapability)
obol network install ethereum --network=mainnet

# List capabilities
obol kubectl get agentcapabilities -A
# or
obol kubectl get acap -A

# Describe capability
obol kubectl describe acap execution-rpc -n ethereum-<id>

Dependencies

  • Depends on: None (can be done in parallel with Traefik migration)
  • Blocks: Orchestrator controller implementation

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions