From 81ff0c2b0e1e8c60695f9db7903aa28d8ee4a361 Mon Sep 17 00:00:00 2001 From: bussyjd Date: Wed, 20 May 2026 18:39:22 +0800 Subject: [PATCH] docs: add token-efficient spec bundle --- ARCHITECTURE.md | 263 ++++++++++++ BEHAVIORS_AND_EXPECTATIONS.md | 114 ++++++ CONTRIBUTING.md | 114 +++--- SPEC.md | 383 ++++++++++++++++++ docs/adr/0001-local-first-kubernetes-stack.md | 20 + docs/adr/0002-litellm-as-model-gateway.md | 20 + .../adr/0003-crds-as-agent-commerce-intent.md | 20 + .../0004-x402-forwardauth-and-seller-proxy.md | 20 + docs/adr/0005-purchaserequest-auth-pool.md | 20 + ...rc8004-discovery-without-central-bazaar.md | 20 + .../0007-agent-crd-hermes-child-runtime.md | 20 + docs/adr/0008-public-tunnel-allowlist.md | 20 + features/agent_runtime.feature | 40 ++ features/buy_side_payments.feature | 46 +++ features/erc8004_discovery.feature | 47 +++ features/obol_payment_asset.feature | 37 ++ features/release_smoke.feature | 37 ++ features/sell_side_monetization.feature | 45 ++ features/stack_lifecycle.feature | 40 ++ features/tunnel_storefront.feature | 35 ++ 20 files changed, 1296 insertions(+), 65 deletions(-) create mode 100644 ARCHITECTURE.md create mode 100644 BEHAVIORS_AND_EXPECTATIONS.md create mode 100644 SPEC.md create mode 100644 docs/adr/0001-local-first-kubernetes-stack.md create mode 100644 docs/adr/0002-litellm-as-model-gateway.md create mode 100644 docs/adr/0003-crds-as-agent-commerce-intent.md create mode 100644 docs/adr/0004-x402-forwardauth-and-seller-proxy.md create mode 100644 docs/adr/0005-purchaserequest-auth-pool.md create mode 100644 docs/adr/0006-erc8004-discovery-without-central-bazaar.md create mode 100644 docs/adr/0007-agent-crd-hermes-child-runtime.md create mode 100644 docs/adr/0008-public-tunnel-allowlist.md create mode 100644 features/agent_runtime.feature create mode 100644 features/buy_side_payments.feature create mode 100644 features/erc8004_discovery.feature create mode 100644 features/obol_payment_asset.feature create mode 100644 features/release_smoke.feature create mode 100644 features/sell_side_monetization.feature create mode 100644 features/stack_lifecycle.feature create mode 100644 features/tunnel_storefront.feature diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4416a69 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,263 @@ +# Obol Stack Architecture + +**Version**: 1.0.0-draft +**Status**: Draft +**Last Updated**: 2026-05-20 + +Visual companion to [SPEC.md](SPEC.md). This file is intentionally compact: use it to orient agents, then jump to the referenced SPEC sections. + +## 1. Principles + +| ID | Principle | Consequence | +|----|-----------|-------------| +| AP1 | Local-first control plane | k3d/k3s, local config dirs, embedded charts, CRDs as state. | +| AP2 | Standards-native commerce | x402 for payments, ERC-8004 for discovery, no central bazaar. | +| AP3 | Intent via CRDs | CLI writes intent; controllers converge resources and status. | +| AP4 | Bounded buyer risk | Pre-signed auth pools, no runtime signer in x402-buyer. | +| AP5 | Public by allowlist | Tunnel exposes only catalog, registration, and paid service routes. | + +## 2. System Context + +```mermaid +flowchart LR + Operator["Operator"] --> CLI["obol CLI"] + Seller["Service Provider"] --> CLI + Buyer["Buyer Agent"] --> BuySkill["buy-x402 skill"] + CLI --> Stack["Local Obol Stack"] + BuySkill --> Stack + Stack --> Ollama["Host Ollama or provider APIs"] + Stack --> Facilitator["x402 Facilitator"] + Stack --> Registry["ERC-8004 Identity Registry"] + Indexer["Permissionless Indexer"] --> Registry + Indexer --> WellKnown["/.well-known/agent-registration.json"] + Buyer --> PublicRoutes["Public tunnel routes"] + PublicRoutes --> Stack +``` + +References: SPEC 1, 3. + +## 3. Runtime Containers + +```mermaid +flowchart TB + subgraph Host["Host machine"] + CLI["obol CLI"] + DataDir["config and data dirs"] + HostOllama["Ollama or OpenAI-compatible server"] + end + + subgraph Cluster["k3d or k3s cluster"] + subgraph LLM["namespace: llm"] + LiteLLM["LiteLLM"] + Buyer["x402-buyer sidecar"] + BuyerCM["buyer config and auth ConfigMaps"] + OllamaSvc["ollama Service and Endpoints"] + end + + subgraph X402["namespace: x402"] + Verifier["x402-verifier"] + Controller["serviceoffer-controller"] + Catalog["skill.md and services.json httpd"] + IdentityDoc["registration document httpd"] + end + + subgraph Traefik["namespace: traefik"] + Gateway["Traefik Gateway"] + Cloudflared["cloudflared"] + Storefront["public storefront"] + end + + subgraph DefaultAgent["namespace: hermes-obol-agent"] + MasterHermes["default Hermes"] + MasterSigner["remote-signer"] + end + + subgraph ChildAgent["namespace: agent-name"] + ChildHermes["child Hermes"] + ChildPVC["hermes-data PVC"] + ChildSigner["optional remote-signer"] + end + end + + CLI --> DataDir + CLI --> Controller + DataDir --> ChildPVC + HostOllama --> OllamaSvc --> LiteLLM + LiteLLM --> Buyer + Controller --> Verifier + Controller --> Catalog + Controller --> IdentityDoc + Gateway --> Verifier + Cloudflared --> Gateway + Storefront --> Catalog + MasterHermes --> LiteLLM + ChildHermes --> LiteLLM +``` + +References: SPEC 2, 4. + +## 4. Module Decomposition + +| Module | Runtime | State | SPEC | +|--------|---------|-------|------| +| CLI | host process | config dir, kube API | 3 | +| Stack lifecycle | host process plus Helmfile | stack ID, kubeconfig, defaults | 5.1 | +| LiteLLM | `llm` Deployment | ConfigMap, Secret | 5.2 | +| Agent CRD reconciler | serviceoffer-controller | Agent status, child resources | 5.3 | +| ServiceOffer reconciler | serviceoffer-controller | ServiceOffer status, routes | 5.4 | +| x402 verifier/proxy | `x402-verifier` | in-memory route rules | 3.3, 5.4 | +| PurchaseRequest reconciler | serviceoffer-controller | ConfigMaps, LiteLLM route | 5.6 | +| x402-buyer | LiteLLM sidecar | auth ConfigMap, pod-local consumed state | 5.6 | +| ERC-8004 renderer | serviceoffer-controller plus httpd | AgentIdentity status, registration doc | 5.7 | +| Tunnel/storefront | cloudflared plus Next.js | tunnel state, storefront resources | 5.8 | + +## 5. Sell Agent Flow + +```mermaid +sequenceDiagram + participant O as Operator + participant CLI as obol CLI + participant API as Kubernetes API + participant C as serviceoffer-controller + participant A as Agent namespace + participant V as x402-verifier + participant T as Traefik + participant B as Buyer + + O->>CLI: obol agent new quant --model M --skills S --create-wallet + CLI->>API: apply Namespace and Agent + C->>API: watch Agent + C->>A: apply Hermes PVC, ConfigMap, Secret, Deployment, Service + C->>A: optional remote-signer + C->>API: update Agent status endpoint, wallet, Ready + O->>CLI: obol sell agent quant --price P --token OBOL + CLI->>API: apply ServiceOffer type=agent + C->>API: resolve Agent ref + C->>API: write status.agentResolution + C->>T: apply HTTPRoute and ReferenceGrant + V->>API: watch ServiceOffer routes + B->>T: unpaid request + T->>V: ForwardAuth + V-->>B: 402 with agentModel, agentSkills, agentRuntime +``` + +References: SPEC 5.3, 5.5. + +## 6. Sell HTTP/Inference Flow + +```mermaid +sequenceDiagram + participant O as Operator + participant CLI as obol CLI + participant API as Kubernetes API + participant C as serviceoffer-controller + participant U as Upstream Service + participant V as x402-verifier + participant T as Traefik + + O->>CLI: obol sell http or sell inference + CLI->>API: apply ServiceOffer + C->>API: add finalizer and status + C->>U: GET healthPath + C->>API: apply ReferenceGrant + C->>API: verify x402-verifier Service and Deployment + C->>API: apply HTTPRoute + C->>API: apply RegistrationRequest if enabled + V->>API: watch RoutePublished ServiceOffers + T->>V: ForwardAuth for /services/name/* +``` + +References: SPEC 5.4. + +## 7. Buy Paid Inference Flow + +```mermaid +sequenceDiagram + participant U as User + participant CLI as obol buy inference + participant H as default Hermes pod + participant Py as buy.py + participant API as Kubernetes API + participant C as serviceoffer-controller + participant L as LiteLLM + participant X as x402-buyer + participant S as Seller endpoint + participant F as x402 Facilitator + + U->>CLI: buy endpoint, model, budget + CLI->>S: pricing probe + CLI->>S: optional registration fetch + CLI->>H: exec buy.py buy + Py->>S: probe 402 + Py->>Py: sign bounded auth pool + Py->>API: create PurchaseRequest + C->>S: probe and validate pricing + C->>API: write buyer ConfigMaps + C->>L: add paid/model route + C->>X: reload + H->>L: request model paid/model + L->>X: proxy request + X->>S: request, receive 402, attach X-PAYMENT, retry + S->>F: verify and settle + S-->>X: paid response + X-->>L: response +``` + +References: SPEC 5.6. + +## 8. Discovery and Registration Flow + +```mermaid +sequenceDiagram + participant C as serviceoffer-controller + participant API as Kubernetes API + participant D as registration document httpd + participant R as ERC-8004 registry + participant CLI as obol sell register + participant I as Indexer + + C->>API: ensure AgentIdentity x402/default + C->>API: create RegistrationRequest owner + C->>D: publish agent-registration.json + CLI->>R: submit registration transaction + C->>R: observe matching registration + C->>API: update AgentIdentity status registrations + I->>R: index agentId + I->>D: fetch registration JSON +``` + +References: SPEC 5.7. + +## 9. Public Network Topology + +```mermaid +flowchart LR + Internet["Internet"] --> CF["Cloudflare Tunnel"] + CF --> Gateway["Traefik Gateway"] + Gateway --> Services["/services/*"] + Gateway --> Skill["/skill.md"] + Gateway --> APIJSON["/api/services.json"] + Gateway --> WellKnown["/.well-known/agent-registration.json"] + Gateway --> Home["/ storefront"] + Services --> Verifier["x402-verifier"] + Skill --> Catalog["catalog httpd"] + APIJSON --> Catalog + WellKnown --> Identity["identity httpd"] + Home --> Storefront["Next.js storefront"] +``` + +Internal-only surfaces must remain hostname-restricted to `obol.stack`: frontend, eRPC, LiteLLM, monitoring. + +References: SPEC 3.2, 5.8, 6. + +## 10. Storage + +| Store | Entities | Notes | +|-------|----------|-------| +| Host config dir | stack ID, backend, kubeconfig, tunnel state | Local operator state. | +| Host data dir | Hermes homes, child agent seed files, PVC backing data | Local-path provisioner maps into pods. | +| Kubernetes API | CRDs, child resources, status | Main control-plane state. | +| Secrets | LiteLLM key, remote-signer keystores, API tokens | Namespaced; avoid copying into docs/logs. | +| ConfigMaps | LiteLLM config, buyer config/auths, catalogs | Controller writes per-purchase keys. | +| Pod emptyDir | x402-buyer consumed state | Reason LiteLLM replicas stay at 1. | +| Chain | ERC-8004 identity NFT/URI | Observed by controller, not minted by controller. | diff --git a/BEHAVIORS_AND_EXPECTATIONS.md b/BEHAVIORS_AND_EXPECTATIONS.md new file mode 100644 index 0000000..65834f0 --- /dev/null +++ b/BEHAVIORS_AND_EXPECTATIONS.md @@ -0,0 +1,114 @@ +# Obol Stack - Behaviors and Expectations + +**Version**: 1.0.0-draft +**Status**: Draft +**Last Updated**: 2026-05-20 + +Behavior contract for [SPEC.md](SPEC.md). Every ID below should map to at least one scenario under [features/](features/). + +## 1. Desired Behaviors + +### 1.1 Stack and Models + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B1 | Operator runs `obol stack init` on a clean config dir. | Stack ID, backend choice, kube defaults, and embedded infra are written. | Stack state must be reproducible. | 5.1 | +| B2 | Operator runs `obol stack up` after init. | k3d/k3s starts, kubeconfig is written, infra syncs, host DNS is configured best-effort. | A single command should converge the local stack. | 5.1 | +| B3 | Operator configures models. | LiteLLM config persists provider models and Hermes can be synced to the current inventory. | Agents need a stable OpenAI-compatible route. | 5.2 | +| B4 | Operator prefers a model. | Default agent-model selection uses the preferred order and skips only literal `paid/*`. | Purchased concrete `paid/` entries are valid. | 5.2 | + +### 1.2 Agent Runtime + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B5 | Operator creates `obol agent new ` with CRD flags. | CLI seeds host files, applies namespace and `Agent`, then controller provisions Hermes. | Child agents are durable K8s resources. | 5.3 | +| B6 | Agent has empty first-time model. | Controller sets `ModelUnpinned` and does not mark Ready. | Silent model selection would hide bad setup. | 4.2 | +| B7 | Agent requests wallet creation. | Controller creates/reuses keystore Secret, remote-signer Deployment/Service, and publishes wallet address. | Agent revenue or signing address must be stable. | 4.2 | +| B8 | Agent is deleted. | Finalizer tears down child resources before removing the CR. | Avoid orphaned child runtimes. | 4.2 | + +### 1.3 Sell-Side Commerce + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B9 | Seller creates `ServiceOffer` for HTTP/inference. | Controller checks upstream, applies payment gate and route, then status conditions converge. | Sell intent must become a paid route. | 5.4 | +| B10 | Seller creates `ServiceOffer type=agent`. | Controller waits for referenced Agent, then derives upstream/model and writes `agentResolution`. | Buyers must know the actual agent model/skills. | 5.5 | +| B11 | Buyer probes unpaid paid route. | Response is HTTP 402 with x402 v2 pricing and asset metadata. | Payment discovery is the protocol entry point. | 3.3 | +| B12 | Agent-backed offer is probed. | 402 includes `agentModel`, `agentSkills`, and `agentRuntime`. | MVP utility is "pay for a Hermes agent turn on this model". | 3.3 | +| B13 | Offer is paused. | Payment gate and route are removed or disabled; offer does not appear in public catalog. | Pausing must stop new paid traffic. | 7.1 | +| B14 | Offer is deleted. | Finalizer removes route children and registration cleanup/tombstone runs when needed. | Public discovery must not retain stale live services. | 5.4, 5.7 | + +### 1.4 Buy-Side Commerce + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B15 | Buyer runs `obol buy inference`. | Host preflight probes pricing, optionally verifies identity, then dispatches to in-pod `buy.py`. | Signing stays with the agent wallet path. | 5.6 | +| B16 | `buy.py` creates PurchaseRequest with auths. | Controller validates price, writes buyer config/auth ConfigMaps, adds `paid/`, reloads sidecar. | Paid model calls should become transparent to the agent. | 5.6 | +| B17 | Agent calls LiteLLM using `paid/`. | x402-buyer maps to remote model, attaches payment, retries, and forwards seller response/body/status. | Paid remote inference must behave like local inference. | 5.6 | +| B18 | Auth pool has no auths. | PurchaseRequest is not Ready and sidecar status shows no remaining spend. | Max loss is bounded by pre-signed auth count. | 4.3 | +| B19 | PurchaseRequest is deleted with remaining auths. | Controller drains or waits before removing route/config. | Deletion must not silently orphan spendable auths. | 4.3 | + +### 1.5 Discovery and Tunnel + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B20 | Registration enabled. | Controller publishes registration JSON and creates RegistrationRequest. | ERC-8004 discovery must be standards-native. | 5.7 | +| B21 | On-chain registration appears. | Controller updates AgentIdentity and ServiceOffer status with agentId/tx hash. | Indexers need chain-bound identity data. | 5.7 | +| B22 | Registration tx is pending but route is live. | `/skill.md` and `/api/services.json` include the offer with `registrationPending=true`. | Usable paid services should not disappear from the storefront. | 4.6 | +| B23 | Tunnel starts or restarts. | Tunnel URL propagates to ConfigMap, agent base URL, catalog, and storefront. | Public endpoints must be current. | 5.8 | +| B24 | Public user opens tunnel root. | Storefront renders services from `/api/services.json`. | Public entrypoint is the usable catalog, not a blank gateway. | 5.8 | + +### 1.6 OBOL Payment Asset + +| ID | Trigger | Expected | Rationale | SPEC | +|----|---------|----------|-----------|------| +| B25 | Seller uses `--token OBOL`. | ServiceOffer includes OBOL address, 18 decimals, Permit2 transfer method, and EIP-712 domain. | Buyers must sign the right asset. | 4.1, 6 | +| B26 | Buyer selects token that differs from seller pricing. | Buy preflight fails with asset mismatch and suggests the correct token when known. | Prevent wrong-asset auth pools. | 5.6 | + +## 2. Undesired Behaviors + +| ID | Trigger | Expected Instead | Risk | SPEC | +|----|---------|------------------|------|------| +| U1 | Public tunnel route exposes frontend, eRPC, LiteLLM, or monitoring. | Keep those routes hostname-restricted to `obol.stack`. | Public local control-plane exposure. | 6 | +| U2 | x402-buyer receives private keys or remote-signer URL. | Only pre-signed auths may reach the sidecar. | Unbounded buyer wallet loss. | 6 | +| U3 | ForwardAuth route settles raw direct `X-PAYMENT` as production path. | Use x402-buyer or standalone `sell inference` settlement path. | Verify-only route can mislead settlement semantics. | 1.1, 5.6 | +| U4 | Code treats `PurchaseRequest` as escrow. | Treat it as bounded authorization inventory. | Incorrect economic/security assumptions. | 1.1, 4.3 | +| U5 | `spec.skills` is documented as confinement. | Document it as seed data only. | Regulated service may overclaim safety. | 1.1, 6 | +| U6 | Controller mints ERC-8004 registration transactions. | Operator submits with `obol sell register`; controller observes. | Hidden custody/gas side effects. | 5.7 | +| U7 | Multiple x402-buyer replicas share one local auth state. | Keep LiteLLM replicas at 1 until state is shared. | Double-spend or inconsistent counters. | 8 | +| U8 | Offer enters public catalog while paused, deleting, or route/payment/upstream false. | Exclude from catalog. | Buyers discover unusable services. | 4.6 | + +## 3. Edge Cases + +| ID | Scenario | Expected Handling | SPEC | +|----|----------|-------------------|------| +| E1 | ServiceOffer references missing Agent. | `WaitingForAgent`, no route publication. | 5.5 | +| E2 | Agent CR is terminating and demo sell reruns. | CLI reports terminating Agent and suggests wait or force delete. | 5.5 | +| E3 | Upstream health returns 5xx. | `UpstreamHealthy=False`, route not published. | 5.4 | +| E4 | `x402-verifier` has no pricing file. | Starts with defaults and waits for kube route source. | 5.4 | +| E5 | Direct verifier call lacks `X-Forwarded-Uri`. | Verifier returns 403 fail-closed. | 3.3 | +| E6 | Quick tunnel URL changes. | Destructive actions warn; restart syncs dependent surfaces. | 5.8 | +| E7 | Registration owner is another offer. | Non-owner offer follows owner RegistrationRequest status. | 5.7 | +| E8 | Buy endpoint returns non-402 during probe. | PurchaseRequest `Probed=False NotPaymentGated`; host preflight fails. | 5.6 | +| E9 | Duplicate PurchaseRequest model exists. | New request `Configured=False DuplicateModel`. | 4.3 | +| E10 | OBOL requested on unsupported chain. | Token resolution fails before ServiceOffer/PurchaseRequest is accepted. | 6 | + +## 4. Performance Expectations + +| ID | Behavior | Target | Degradation Handling | +|----|----------|--------|----------------------| +| P1 | Controller convergence | Requeue non-ready offers/purchases every 5s. | Status explains blocked condition. | +| P2 | Upstream health | 2s per probe. | Mark unhealthy and retry. | +| P3 | Buy pricing probe | 15s. | Fail preflight or mark `Probed=False`. | +| P4 | Registration fetch | 5s. | User can retry with `--no-verify-identity` in dev. | +| P5 | Tunnel readiness | 5 minutes default. | Return explicit rollout/log error. | + +## 5. Guardrails + +| ID | Rule | Enforcement | Violation Response | +|----|------|-------------|--------------------| +| G1 | No new contracts for MVP features. | Design review and ADR. | Reject spec/code path. | +| G2 | Public tunnel allowlist only. | Route templates, tests, review. | Block merge. | +| G3 | Buyer sidecar has no signer. | Deployment env/volume review and tests. | Block merge. | +| G4 | Agent CRD status must come from controller. | CLI only applies spec. | Block direct status writes outside tests. | +| G5 | OBOL asset metadata must stay in token registry and offer/payment specs. | Token tests and buy preflight. | Reject incomplete token path. | +| G6 | Spec changes with behavior changes. | PR checklist and BDD references. | Request docs/tests update. | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fad91f6..2aeabcd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,90 +1,74 @@ -# Contributing to Blockchain Helm Charts +# Obol Stack - Developer Rules -Thank you for considering contributing to this project! This document provides guidelines to help you contribute effectively. +These rules are non-negotiable for implementation work. The source specs are [SPEC.md](SPEC.md), [ARCHITECTURE.md](ARCHITECTURE.md), and [BEHAVIORS_AND_EXPECTATIONS.md](BEHAVIORS_AND_EXPECTATIONS.md). -## Getting Started +## 1. Keep Specs and Behavior Together -### Prerequisites +Any behavior change must update at least one of: `SPEC.md`, `BEHAVIORS_AND_EXPECTATIONS.md`, `features/*.feature`, or `docs/adr/*.md`. -- Kubernetes knowledge -- Helm chart development experience -- Understanding of the specific blockchain client you're creating/modifying a chart for +Run before handing off docs: -### Development Environment +```bash +git diff --check +``` -1. Install [Helm](https://helm.sh/docs/intro/install/) -2. Install [kubectl](https://kubernetes.io/docs/tasks/tools/) -3. Set up a Kubernetes environment (minikube, kind, or a cloud provider) +## 2. Do Not Add MVP Smart Contracts -## Chart Development Guidelines +The MVP uses deployed ERC-20, Permit2/EIP-3009, x402, and ERC-8004 contracts. Do not introduce new contracts for escrow, fees, staking, slashing, or registry logic without a new ADR. -### Chart Structure +## 3. Treat CRDs as Intent -Each chart should follow this structure: -``` -charts// -├── Chart.yaml -├── values.yaml -├── templates/ -│ ├── deployment.yaml -│ ├── service.yaml -│ ├── configmap.yaml -│ ├── secret.yaml (if needed) -│ ├── pvc.yaml (if needed) -│ └── NOTES.txt -├── OWNERS (maintainers list) -└── README.md (chart-specific documentation) -``` +CLIs apply specs. Controllers own status and child resources. Do not mark CRDs ready from CLI code except in tests. -### Requirements +Key CRDs: -- Charts must be compatible with Helm 3 -- Include comprehensive documentation -- Provide sensible defaults in values.yaml -- Include proper Kubernetes resource requests and limits -- Follow security best practices +- `ServiceOffer` +- `PurchaseRequest` +- `Agent` +- `AgentIdentity` +- `RegistrationRequest` -### Values.yaml +## 4. Keep Buyer Runtime Signer-Free -- Group related values logically -- Add comments explaining the purpose of values -- Include sensible defaults that work out-of-the-box -- Provide examples for custom configurations +`x402-buyer` must never receive a private key, seed phrase, remote-signer URL, or wallet keystore. It may only receive pre-signed auths and public payment metadata. -## Pull Request Process +## 5. Do Not Call PurchaseRequest Escrow -1. Fork the repository -2. Create a new branch for your changes -3. Make your changes following the chart development guidelines -4. Test your charts thoroughly -5. Submit a pull request -6. Address review comments +`PurchaseRequest` is a bounded pre-signed authorization pool. Naming, docs, UI copy, and tests must preserve that distinction. -### Pull Request Checklist +## 6. Preserve the Public Tunnel Allowlist -- [ ] Chart version updated according to semantic versioning -- [ ] Chart README.md updated with any new values or changes -- [ ] Chart has been tested and verified to work -- [ ] `helm lint` passes without warnings -- [ ] `helm template` generates valid Kubernetes resources +Public tunnel routes may expose only: -## Testing Your Chart +- `/services/*` +- `/skill.md` +- `/api/services.json` +- `/.well-known/agent-registration.json` +- `/` -```bash -# Lint the chart -helm lint charts/your-chart +Frontend, eRPC, LiteLLM, and monitoring must remain local-only or hostname-restricted to `obol.stack`. -# Render the templates -helm template charts/your-chart +## 7. Do Not Overclaim Agent Isolation -# Install the chart in a test environment -helm install test-release charts/your-chart --dry-run -``` +`Agent.spec.skills` seeds skills into a Hermes profile/home. It is not sandboxing. Any service that claims privacy, medical safety, legal safety, or tool confinement needs explicit policy/sandbox implementation and tests. + +## 8. Keep OBOL Asset Metadata Complete -## Code of Conduct +OBOL-priced offers must carry address, symbol, decimals, transfer method, and EIP-712 domain metadata. Buyer paths must reject wrong-token purchases before signing. + +## 9. Use Existing Flow Harnesses + +Prefer existing Go tests and `flows/` before adding ad hoc scripts. + +Common checks: + +```bash +go test ./cmd/obol ./internal/serviceoffercontroller ./internal/x402 ./internal/x402/buyer ./internal/erc8004 ./internal/agentcrd ./internal/agentruntime ./internal/tunnel -count=1 +bash -n flows/*.sh +``` -Please respect other contributors and maintain a positive environment for everyone. +Integration gates live in `flows/README.md`; do not duplicate flow plumbing. -## Thank You +## 10. Keep Generated Specs Token-Efficient -Your contributions help make this project better for everyone! +Use stable IDs, compact tables, and cross-links. Do not paste long code excerpts or repeated architecture prose into spec docs. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..2289cfe --- /dev/null +++ b/SPEC.md @@ -0,0 +1,383 @@ +# Obol Stack Technical Specification + +**Version**: 1.0.0-draft +**Status**: Draft +**Last Updated**: 2026-05-20 +**Audience**: coding agents and maintainers + +This is the authoritative, token-efficient implementation spec for Obol Stack. Prefer section IDs over copied prose when prompting agents. Visuals live in [ARCHITECTURE.md](ARCHITECTURE.md). Test oracles live in [BEHAVIORS_AND_EXPECTATIONS.md](BEHAVIORS_AND_EXPECTATIONS.md) and [features/](features/). + +## 1. Scope + +### 1.1 Product Contract + +Obol Stack is a local-first Kubernetes stack that lets AI agents run infrastructure, expose paid services, publish discovery metadata, and buy paid remote inference through x402. + +| ID | In Scope | +|----|----------| +| S1 | Local stack lifecycle on k3d or standalone k3s. | +| S2 | LiteLLM model routing over host Ollama, cloud providers, and purchased `paid/` routes. | +| S3 | Hermes as default agent runtime; OpenClaw remains supported as a legacy/optional runtime. | +| S4 | Declarative `Agent` CRD for durable Hermes sub-agents with seeded skills, soul, PVC, API key, and optional wallet. | +| S5 | Sell-side `ServiceOffer` CRD for x402-gated HTTP, inference, fine-tuning, and agent-backed services. | +| S6 | Buy-side `PurchaseRequest` CRD as a bounded pre-signed auth pool for paid inference. | +| S7 | ERC-8004 identity publication through `AgentIdentity` and `RegistrationRequest`. | +| S8 | Public Cloudflare tunnel, storefront, `/skill.md`, `/api/services.json`, and `/.well-known/agent-registration.json`. | +| S9 | Release and smoke validation through `flows/` and Go tests. | + +| ID | Anti-Scope | +|----|------------| +| A1 | No centralized service bazaar or custodial marketplace. Discovery is standards-native. | +| A2 | No new smart contracts for the MVP. Use deployed ERC-20, Permit2, x402, and ERC-8004 contracts only. | +| A3 | No slashing, escrow, or court/arbitration logic in phase 1. Reputation and future income are the penalty surface. | +| A4 | `PurchaseRequest` is not escrow. It is a bounded pre-signed authorization pool. | +| A5 | Raw direct `X-PAYMENT` through Traefik ForwardAuth is not the production buy path. Use `x402-buyer` or standalone `sell inference`. | +| A6 | `spec.skills` seeds an agent. It is not a security sandbox. | +| A7 | The stack does not guarantee medical, legal, or other regulated policy behavior without explicit skills and middleware. | + +### 1.2 Actors + +| Actor | Goal | Primary Interfaces | +|-------|------|--------------------| +| Operator | Run stack, models, agents, tunnel. | `obol stack`, `obol model`, `obol agent`, `obol tunnel` | +| Service Provider | Sell model, HTTP, or agent turns. | `obol sell *`, `ServiceOffer` | +| Buyer Agent | Discover, verify, and pay for remote services. | `obol buy inference`, `buy-x402`, `PurchaseRequest` | +| Controller | Reconcile CRDs into cluster resources and status. | informers, dynamic client | +| x402 Facilitator | Verify and settle token auths. | x402 HTTP API | +| ERC-8004 Indexer | Discover agent identity and services. | on-chain registry plus registration JSON | + +### 1.3 Glossary + +| Term | Definition | +|------|------------| +| Agent | Namespaced `obol.org/v1alpha1` CRD representing a durable Hermes runtime. | +| AgentIdentity | Durable operator identity document. Outlives ServiceOffers and can tombstone. | +| Agent Resolution | Controller-populated view of an agent offer: model, skills, runtime, endpoint. | +| ForwardAuth | Traefik auth hook used by `x402-verifier` to deny unpaid requests with HTTP 402. | +| Hermes | Default agent runtime image `nousresearch/hermes-agent:v2026.5.7`. | +| Mother Agent | Operator-controlled agent that may create child `Agent` CRs. Product concept; current code exposes the CRD and CLI substrate. | +| PurchaseRequest | Buyer-side CRD declaring endpoint, model, payment terms, and pre-signed auth pool. | +| ServiceOffer | Seller-side CRD declaring paid service intent and discovery metadata. | +| x402-buyer | LiteLLM sidecar that spends pre-signed auths and proxies paid inference. | +| x402-verifier | Sell-side verifier/proxy that emits 402 and gates or settles paid routes. | + +### 1.4 Hard Constraints + +| ID | Constraint | Impact | +|----|------------|--------| +| C1 | No additional smart contracts. | Use token registry, Permit2/EIP-3009, ERC-8004 Identity Registry. | +| C2 | Local-first stack. | State is local config, host data dirs, Kubernetes CRDs, PVCs, ConfigMaps, Secrets. | +| C3 | Seller gets 100 percent x402 payment. | No protocol middleman fee logic in stack. | +| C4 | Buyer runtime has zero signer access. | Agent signs auths before creating `PurchaseRequest`; sidecar only consumes auths. | +| C5 | Public tunnel must expose only safe surfaces. | Public routes: `/services/*`, `/skill.md`, `/api/services.json`, `/.well-known/agent-registration.json`, storefront `/`. | +| C6 | Agent skills are seed data. | Do not advertise `spec.skills` as confinement. Use separate sandbox/policy work for regulated services. | +| C7 | Single x402-buyer pod. | LiteLLM deployment remains replicas=1 while consumed-auth state is pod-local. | +| C8 | Controller is source of status truth. | CLIs apply intent and read status; they do not mark resources ready. | +| C9 | Token metadata must be explicit for non-default assets. | OBOL offers must include asset address, decimals, transfer method, EIP-712 metadata. | +| C10 | Specs are coding-agent inputs. | Keep docs concise, stable IDs, minimal examples, no long narrative duplication. | + +## 2. System Map + +### 2.1 Modules + +| ID | Module | Responsibility | Key Paths | +|----|--------|----------------|-----------| +| M1 | CLI | User command surface. | `cmd/obol/*.go` | +| M2 | Stack lifecycle | k3d/k3s init/up/down/purge, defaults sync, host DNS. | `internal/stack/`, `internal/defaults/`, `internal/dns/` | +| M3 | Model routing | LiteLLM config, providers, rank/prefer, default Hermes model sync. | `internal/model/`, `internal/embed/infrastructure/base/templates/llm.yaml` | +| M4 | Runtime onboarding | Legacy Hermes/OpenClaw instances and default obol-agent. | `internal/hermes/`, `internal/openclaw/`, `internal/agentruntime/` | +| M5 | Agent CRD | Host seed plus controller-provisioned Hermes child runtime. | `internal/agentcrd/`, `internal/serviceoffercontroller/agent*.go` | +| M6 | Sell control plane | Reconcile ServiceOffers into routes, gates, registration, catalogs. | `internal/serviceoffercontroller/`, `internal/monetizeapi/` | +| M7 | Sell data plane | x402 verifier/proxy, 402 metadata, route matching, settlement. | `cmd/x402-verifier/`, `internal/x402/` | +| M8 | Buy control plane | PurchaseRequest reconcile into buyer config/auths and LiteLLM paid route. | `internal/serviceoffercontroller/purchase*.go` | +| M9 | Buy data plane | Sidecar proxy that spends auths and calls paid upstreams. | `cmd/x402-buyer/`, `internal/x402/buyer/` | +| M10 | ERC-8004 | Registration ABI/client, `.well-known` document, on-chain status. | `internal/erc8004/`, `internal/serviceoffercontroller/identity*.go` | +| M11 | Tunnel/storefront | Cloudflared quick/persistent tunnel and public catalog UI. | `internal/tunnel/`, `web/public-storefront/` | +| M12 | QA | User-journey flows and integration tests. | `flows/`, `internal/*_test.go` | + +### 2.2 Runtime Namespaces + +| Namespace | Owner | Main Resources | +|-----------|-------|----------------| +| `llm` | base stack | `ollama` Service+Endpoints, LiteLLM, `x402-buyer`, buyer ConfigMaps | +| `x402` | base stack | `x402-verifier`, `serviceoffer-controller`, catalogs, default `AgentIdentity` | +| `traefik` | base stack | Gateway, cloudflared, public storefront | +| `hermes-obol-agent` | default agent | master Hermes, remote-signer | +| `agent-` | Agent CRD | child Hermes, PVC, API Secret, optional remote-signer | +| service namespace | seller | upstream service and ServiceOffer when not agent-backed | + +## 3. External Interfaces + +### 3.1 CLI Surface + +| Area | Commands | Spec Sections | +|------|----------|---------------| +| Stack | `obol stack init/up/down/purge/status` | 5.1 | +| Models | `obol model setup/status/token/sync/pull/list/prefer/discover/remove` | 5.2 | +| Agents | `obol agent init/new/update/sync/setup/auth/list/delete/wallet` | 5.3 | +| Sell | `obol sell inference/http/agent/demo/list/status/test/stop/update/delete/pricing/register/identity/info` | 5.4, 5.5 | +| Buy | `obol buy inference` | 5.6 | +| Tunnel | `obol tunnel status/restart/stop/logs/login/provision/setup` | 5.8 | +| Networks/Apps | `obol network *`, `obol app *` | existing README/CLI help until this bundle is extended | + +### 3.2 Public HTTP Surface + +| Path | Producer | Purpose | Public? | +|------|----------|---------|---------| +| `/services//*` | ServiceOffer HTTPRoute | Paid x402 service route. | Yes | +| `/skill.md` | serviceoffer-controller | Markdown service catalog. | Yes | +| `/api/services.json` | serviceoffer-controller | Machine-readable service catalog. | Yes | +| `/.well-known/agent-registration.json` | AgentIdentity renderer | ERC-8004 registration document. | Yes | +| `/` | public storefront | Catalog UI for tunnel hostname. | Yes | +| `/rpc`, frontend, LiteLLM | base infra | Local stack surfaces. | No public tunnel exposure | + +### 3.3 x402 402 Contract + +`x402-verifier` returns `x402Version: 2`, `accepts[]`, and asset extensions. For agent-backed offers it must add: + +| Field | Source | +|-------|--------| +| `accepts[].extra.agentModel` | `ServiceOffer.status.agentResolution.model` | +| `accepts[].extra.agentSkills` | `ServiceOffer.status.agentResolution.skills` | +| `accepts[].extra.agentRuntime` | `ServiceOffer.status.agentResolution.runtime` | +| asset transfer method/domain | `ServiceOffer.spec.payment.asset` or chain default | + +## 4. Data Model + +### 4.1 ServiceOffer + +Group/version/kind: `obol.org/v1alpha1`, `ServiceOffer`, namespaced. + +| Field | Required | Meaning | +|-------|----------|---------| +| `spec.type` | no | `http`, `inference`, `fine-tuning`, `agent`; default `http`. | +| `spec.agent.ref.name/namespace` | agent only | Referenced `Agent` CR. | +| `spec.model.name/runtime` | inference/fine-tuning | LLM metadata. Agent offers derive this from Agent status. | +| `spec.upstream.service/namespace/port` | non-agent | In-cluster backend service. | +| `spec.upstream.healthPath` | no | Health probe path; effective default `/`. CRD default says `/health`. | +| `spec.payment.network/payTo/price` | yes | x402 payment terms. | +| `spec.payment.asset.*` | no | Explicit settlement token metadata. Needed for OBOL/Permit2. | +| `spec.path` | no | Public prefix; default `/services/`. | +| `spec.registration.*` | no | ERC-8004 metadata and OASF discovery tags. | +| `spec.provenance` | no | Experiment/model provenance for registration/catalog. | + +Conditions: `ModelReady`, `UpstreamHealthy`, `PaymentGateReady`, `RoutePublished`, `Registered`, `Ready`. + +Agent-only status: `status.agentResolution.{model,skills,runtime,endpoint}`. + +### 4.2 Agent + +Group/version/kind: `obol.org/v1alpha1`, `Agent`, namespaced. CLI convention: name ``, namespace `agent-`. + +| Field | Meaning | +|-------|---------| +| `spec.runtime` | `hermes` only today. | +| `spec.model` | LiteLLM model to pin. Empty keeps status unready until pinned. | +| `spec.skills[]` | Seeded embedded skills. Not sandboxing. | +| `spec.objective` | Seed text rendered into `soul.md` on first host seed. | +| `spec.wallet.create` | Provision per-agent remote-signer and publish `status.walletAddress`. | +| `status.phase` | `Pending`, `Provisioning`, `Ready`, `Failed`. | +| `status.pinnedModel` | Effective model in use. | +| `status.endpoint` | Internal Hermes URL, usually `http://hermes..svc.cluster.local:8642`. | + +Agent child resources: Namespace, ServiceAccount, PVC `hermes-data`, ConfigMap `hermes-config`, Secret `hermes-api-server`, Deployment `hermes`, Service `hermes`, optional remote-signer Secret/Deployment/Service. + +### 4.3 PurchaseRequest + +Group/version/kind: `obol.org/v1alpha1`, `PurchaseRequest`, namespaced. + +| Field | Required | Meaning | +|-------|----------|---------| +| `spec.endpoint` | yes | Full x402-gated inference endpoint. | +| `spec.model` | yes | Remote model exposed locally as `paid/`. | +| `spec.count` | yes | Intended auth count, max 2500. | +| `spec.preSignedAuths[]` | runtime required | Fully signed x402 payments; legacy ERC-3009 fields still supported. | +| `spec.autoRefill.*` | no | Agent-managed refill intent. Controller does not sign. | +| `spec.payment.*` | yes | Network, payTo, atomic price, asset metadata. | +| `status.publicModel` | output | Published LiteLLM model name. | +| `status.remaining/spent` | output | Reconciled sidecar counters, not a live source of truth. | + +Reconcile stages: `Probed`, `AuthsLoaded`, `Configured`, `Ready`, plus `Deleting` during drain. + +### 4.4 RegistrationRequest + +Group/version/kind: `obol.org/v1alpha1`, `RegistrationRequest`, namespaced. + +| Field | Meaning | +|-------|---------| +| `spec.serviceOfferName/Namespace` | Owning offer. | +| `spec.desiredState` | `Active` or `Tombstoned`. | +| `spec.chain` | ERC-8004 network alias. | +| `status.phase` | `Publishing`, `Registering`, `AwaitingExternalRegistration`, `Registered`, `OffChainOnly`, `Tombstoned`. | +| `status.publishedUrl` | Public `.well-known` URL. | +| `status.agentId/registrationTxHash/...` | On-chain registration observation. | + +### 4.5 AgentIdentity + +Group/version/kind: `obol.org/v1alpha1`, `AgentIdentity`, namespaced. Default identity: `x402/default`. + +| Field | Meaning | +|-------|---------| +| `spec` | Empty by design today. | +| `status.registrations[].chain` | Registration chain alias. | +| `status.registrations[].agentId` | ERC-8004 token ID for that chain. | + +Deleting the last offer must tombstone the document instead of deleting identity history. + +### 4.6 Service Catalog JSON + +`/api/services.json` is an array of `schemas.ServiceCatalogEntry`. + +| Field | Meaning | +|-------|---------| +| `name`, `namespace`, `type`, `model` | Offer identity and model metadata. | +| `endpoint` | Full public service URL. | +| `price`, `priceRaw`, `priceUnit`, `priceAtomicUnits` | Display and signing price data. | +| `payTo`, `network`, `caip2Network`, `chainId` | Payment target and chain. | +| `asset.{address,symbol,decimals,transferMethod,eip712Domain}` | Signing metadata. | +| `registrationPending` | Operationally ready but ERC-8004 tx pending. | + +## 5. Core Flows + +### 5.1 Stack Lifecycle + +1. `obol stack init` writes stack ID, backend config, kube defaults, and embedded infra into config dir. +2. `obol stack up` starts k3d/k3s, writes kubeconfig, refreshes defaults, sets hosts, applies Helmfile/base templates, configures LiteLLM/default agent when possible, and warns if public tunnel URL may change. +3. `obol stack down` stops cluster and DNS resolver after quick-tunnel loss confirmation. +4. `obol stack purge --force` destroys cluster and local config/data after wallet backup prompts. + +### 5.2 Model Routing + +1. `obol model setup` configures Ollama, Anthropic, OpenAI, or custom providers into LiteLLM. +2. LiteLLM always includes wildcard `paid/* -> openai/* -> http://127.0.0.1:8402/v1`. +3. `obol model prefer` defines order used by agent default selection. +4. `obol model sync` re-renders stack-managed Hermes config from LiteLLM inventory. + +### 5.3 Agent CRD Creation + +1. `obol agent new --model --skills a,b --objective --create-wallet`. +2. CLI validates name and skills, creates namespace `agent-`, seeds host files under `/agent-/hermes-data/.hermes/`. +3. CLI applies `Agent` CR. Controller adds finalizer, validates, pins model, reads LiteLLM key, applies Hermes resources, optionally provisions remote-signer. +4. Controller sets `status.endpoint`, `status.walletAddress`, and `Ready=True` only when deployment and wallet requirements are satisfied. + +### 5.4 Sell HTTP/Inference + +1. Seller runs `obol sell http` or `obol sell inference`. +2. CLI resolves token/chain/payTo/pricing, ensures CA bundle, applies `ServiceOffer`. +3. Controller adds finalizer, checks model/upstream, applies x402 `ReferenceGrant`, waits for `x402-verifier`, applies `HTTPRoute`. +4. If registration enabled, controller creates or shares a `RegistrationRequest` and publishes identity resources. +5. Ready requires all six ServiceOffer conditions true. Catalog visibility requires operational readiness: model, upstream, payment gate, route. + +### 5.5 Sell Agent + +1. Seller creates an `Agent` or lets interactive `obol sell agent ` create one. +2. `obol sell agent` builds `ServiceOffer{type: agent, spec.agent.ref}` in the agent namespace. It defaults `payTo` to the agent wallet, then host remote-signer, then requires explicit `--pay-to`. +3. Controller resolves the referenced Agent. If missing/not ready, offer stays `WaitingForAgent`. +4. When Agent is ready, controller synthesizes in-memory upstream/model, writes `status.agentResolution`, and normal ServiceOffer reconciliation proceeds. +5. x402 402 responses must advertise model, skills, and runtime through `accepts[].extra`. + +### 5.6 Buy Paid Inference + +1. `obol buy inference` probes seller pricing and optionally verifies ERC-8004 identity. +2. CLI dispatches into the default Hermes pod: `python3 buy-x402/scripts/buy.py buy ...`. +3. `buy.py` signs N auths locally and creates/updates a `PurchaseRequest`. +4. Controller probes endpoint, validates price, loads auths, writes one JSON key per purchase into `x402-buyer-config` and `x402-buyer-auths`, hot-adds `paid/` to LiteLLM, and reloads sidecar. +5. Runtime request to LiteLLM with model `paid/` routes to x402-buyer, which attaches `X-PAYMENT`, retries, and marks auths consumed only after attempt handling. + +### 5.7 ERC-8004 Registration + +1. ServiceOffer registration metadata renders into the shared AgentIdentity document. +2. Controller publishes `/.well-known/agent-registration.json` from a busybox httpd child stack. +3. `obol sell register` submits on-chain registration externally. Controller observes chain via eRPC and writes AgentIdentity status. +4. Multiple offers share `x402/default`. Non-owner offers reference the owner RegistrationRequest status. +5. Tombstone path keeps historical registration but publishes `active:false`, `x402Support:false`. + +### 5.8 Tunnel and Storefront + +1. `obol tunnel restart`, sell commands, or persistent tunnel setup starts cloudflared. +2. Tunnel URL is injected into default agent and `obol-frontend/obol-stack-config`. +3. `CreateStorefront` deploys Next.js storefront in `traefik`, hostname-pinned to tunnel host. +4. Storefront reads `http://obol-skill-md.x402.svc:8080/api/services.json`. + +## 6. Security Model + +| Boundary | Risk | Control | +|----------|------|---------| +| Public internet to cluster | Exposing local dashboard/RPC. | Hostname-restricted internal HTTPRoutes; public routes limited by C5. | +| Buyer to seller | Non-payment or replay. | x402 facilitator verification, per-route price/asset, auth consumption tracking. | +| Buyer sidecar | Signer theft. | Sidecar receives only pre-signed auths, no private key or remote-signer URL. | +| Agent runtime | Prompt/tool abuse. | Current MVP seeds skills but does not sandbox; high-risk services require separate policy/sandbox. | +| Controller RBAC | Over-broad cluster mutation. | Controller owns CRD child resources; agent gets minimal compatibility CRUD/read. | +| ERC-8004 identity | Fake service endpoint. | Buyer verifies service endpoint and agent ID against priced chain. | +| OBOL payments | Wrong asset or signing domain. | Token registry and explicit asset metadata in ServiceOffer/PurchaseRequest. | +| Tunnel | Stale quick URL. | Tunnel state sync, restart propagation, quick-tunnel loss warning. | + +## 7. Error and Status Contract + +### 7.1 ServiceOffer Reasons + +| Condition | Common Reasons | Recovery | +|-----------|----------------|----------| +| `ModelReady=False` | `MissingModel`, `WaitingForAgent` | Pin model or wait for Agent. | +| `UpstreamHealthy=False` | `MissingService`, `Unhealthy`, `WaitingForAgent` | Fix Service/health path/runtime. | +| `PaymentGateReady=False` | `WaitingForUpstream`, `ApplyFailed`, `WaitingForGateway`, `Paused` | Fix upstream, verifier, RBAC, or unpause. | +| `RoutePublished=False` | `WaitingForPaymentGate`, `ApplyFailed`, `Paused` | Fix payment gate or route apply issue. | +| `Registered=False` | `Pending`, `AwaitingExternalRegistration`, `IdentityError`, `WaitingForRoute` | Publish/fund/register or inspect identity resources. | +| `Ready=False` | `Reconciling`, `WaitingForAgent` | Inspect the first false dependency condition. | + +### 7.2 Agent Reasons + +| Condition | Common Reasons | Recovery | +|-----------|----------------|----------| +| `Validated=False` | `UnsupportedRuntime`, `InvalidSkillEntry` | Fix spec. | +| `Provisioned=False` | `ModelUnpinned`, `ProvisionError`, `WaitingForDeployment` | Pin model, inspect deployment/events. | +| `Ready=False` | `WalletError`, `WaitingForWallet`, `WaitingForDeployment` | Inspect remote-signer Secret/Deployment and status. | + +### 7.3 PurchaseRequest Reasons + +| Condition | Common Reasons | Recovery | +|-----------|----------------|----------| +| `Probed=False` | `ProbeError`, `NotPaymentGated`, `InvalidPricing`, `PricingMismatch` | Fix endpoint/model/payment terms. | +| `AuthsLoaded=False` | `NoAuths` | Ensure `buy.py` embedded signed auths. | +| `Configured=False` | `DuplicateModel`, `ConfigWriteError`, `AuthsWriteError`, `NoAuths` | Rename purchase/model or inspect ConfigMaps. | +| `Ready=False` | `SidecarNotReady`, `RuntimeSyncing` | Wait/reload sidecar or inspect `/status`. | +| `Deleting=True` | `Draining`, `RuntimeCleanupPending` | Wait for remaining auths or sidecar removal. | + +## 8. Performance and Ops Targets + +| ID | Target | Measurement | +|----|--------|-------------| +| P1 | Controller requeues converging offers/purchases every 5s. | Status transitions without spec mutation. | +| P2 | Cloudflared readiness budget defaults to 5 minutes. | `tunnel.WaitReady`. | +| P3 | Upstream health probe timeout is 2s. | ServiceOffer `UpstreamHealthy`. | +| P4 | Purchase pricing probe timeout is 15s. | `PurchaseRequest` and host buy preflight. | +| P5 | Registration document fetch timeout is 5s. | `obol buy inference` identity preflight. | +| P6 | x402-buyer remains single-replica until consumed auth state is shared. | LiteLLM deployment replicas. | +| P7 | Public catalog excludes paused, deleting, and operationally-not-ready offers. | `/skill.md`, `/api/services.json`. | + +## 9. Testing Strategy + +| Level | Required Coverage | +|-------|-------------------| +| Unit | CLI flag validation, CRD rendering, route matching, token registry, buyer encoding, ERC-8004 ABI/client, tunnel state. | +| Controller | ServiceOffer, Agent, PurchaseRequest, AgentIdentity, renderers, finalizers, cleanup. | +| BDD/integration | x402 local fork, seller/buyer path, tunnel path, discovery. | +| Smoke flows | `flows/flow-01` through `flow-16` plus `flows/release-smoke.sh`. | + +Focused commands: + +```bash +go test ./cmd/obol ./internal/serviceoffercontroller ./internal/x402 ./internal/x402/buyer ./internal/erc8004 ./internal/agentcrd ./internal/agentruntime ./internal/tunnel -count=1 +go test -tags integration -v -run TestBDDIntegration -timeout 20m ./internal/x402/ +bash -n flows/*.sh +git diff --check +``` + +## 10. Phases + +| Phase | Scope | Exit Criteria | +|-------|-------|---------------| +| Phase 0 | Current main: stack, sell HTTP/inference/agent, buy inference, OBOL/USDC, ERC-8004, tunnel catalog. | Unit tests and relevant flow pass. | +| Phase 1 | Mother-agent product hardening: in-cluster agent factory flow, approval cards, budget policy, lifecycle cleanup UX. | Permissioned manager can create/update/delete child Agents without host CLI dependency. | +| Phase 2 | Service quality and reputation: benchmarks, reputation weighting, provider staking signal, anti-gaming policy. | Indexers can rank offers using verifiable metadata without centralized curation. | +| Phase 3 | Advanced payment economics: optional escrow-like x402 primitives if standard matures; LP/revenue automation off-chain first. | No new contracts, simulations demonstrate stakeholder-positive behavior. | diff --git a/docs/adr/0001-local-first-kubernetes-stack.md b/docs/adr/0001-local-first-kubernetes-stack.md new file mode 100644 index 0000000..e22b836 --- /dev/null +++ b/docs/adr/0001-local-first-kubernetes-stack.md @@ -0,0 +1,20 @@ +# ADR-0001: Local-First Kubernetes Stack + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 2, 5.1, 6 + +## Context + +Obol Stack must run on an operator's machine while still exercising real Kubernetes primitives used by agents, gateways, controllers, and tunnels. + +## Decision + +Use k3d as the default backend and standalone k3s as the bare-metal backend. Persist stack identity, backend choice, kubeconfig, defaults, and data under local config/data dirs. + +## Consequences + +- Positive: real Kubernetes APIs and CRDs are available locally. +- Positive: operator can test seller/buyer flows without cloud infra. +- Negative: Kubernetes remains an operational dependency. +- Neutral: host data paths and local-path provisioning are part of the runtime contract. diff --git a/docs/adr/0002-litellm-as-model-gateway.md b/docs/adr/0002-litellm-as-model-gateway.md new file mode 100644 index 0000000..c952c9e --- /dev/null +++ b/docs/adr/0002-litellm-as-model-gateway.md @@ -0,0 +1,20 @@ +# ADR-0002: LiteLLM as Model Gateway + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 5.2, 5.6 + +## Context + +Agents need a stable OpenAI-compatible API across host Ollama, cloud providers, and paid remote inference. + +## Decision + +Use LiteLLM in the `llm` namespace as the model gateway. Keep a static `paid/*` route to the local `x402-buyer` sidecar and add concrete provider or purchased models through config/controller updates. + +## Consequences + +- Positive: Hermes can use one OpenAI-compatible provider path. +- Positive: purchased remote inference is called as `paid/`. +- Negative: the Obol LiteLLM fork is part of the deployment contract. +- Neutral: x402-buyer consumed state currently requires LiteLLM replicas=1. diff --git a/docs/adr/0003-crds-as-agent-commerce-intent.md b/docs/adr/0003-crds-as-agent-commerce-intent.md new file mode 100644 index 0000000..cc37f45 --- /dev/null +++ b/docs/adr/0003-crds-as-agent-commerce-intent.md @@ -0,0 +1,20 @@ +# ADR-0003: CRDs as Agent Commerce Intent + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 4, 5.3, 5.4, 5.6, 5.7 + +## Context + +CLI commands, agents, controllers, and tests need a shared source of truth for services, purchases, identities, and child agents. + +## Decision + +Represent durable intent as Kubernetes CRDs: `ServiceOffer`, `PurchaseRequest`, `Agent`, `AgentIdentity`, and `RegistrationRequest`. CLIs apply spec; controllers own status and child resources. + +## Consequences + +- Positive: agents and humans can inspect and mutate intent with standard K8s tools. +- Positive: controllers can converge status after restarts. +- Negative: CRD schema changes must be carefully migrated. +- Neutral: host-side helpers may seed files, but runtime readiness is controller-owned. diff --git a/docs/adr/0004-x402-forwardauth-and-seller-proxy.md b/docs/adr/0004-x402-forwardauth-and-seller-proxy.md new file mode 100644 index 0000000..314fed2 --- /dev/null +++ b/docs/adr/0004-x402-forwardauth-and-seller-proxy.md @@ -0,0 +1,20 @@ +# ADR-0004: x402 ForwardAuth and Seller Proxy + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 3.3, 5.4, 6 + +## Context + +Public service routes need an unpaid discovery response, payment verification, and successful-response settlement semantics. + +## Decision + +Use `x402-verifier` as both Traefik ForwardAuth gate and seller-owned proxy. ForwardAuth remains verify-only for legacy/gateway integration; the seller proxy path settles after upstream success. + +## Consequences + +- Positive: unpaid probes receive standard x402 402 responses. +- Positive: seller proxy can settle only after upstream success. +- Negative: direct raw `X-PAYMENT` through ForwardAuth is not a complete production path. +- Neutral: route rules are built dynamically from `ServiceOffer` status. diff --git a/docs/adr/0005-purchaserequest-auth-pool.md b/docs/adr/0005-purchaserequest-auth-pool.md new file mode 100644 index 0000000..763d6e6 --- /dev/null +++ b/docs/adr/0005-purchaserequest-auth-pool.md @@ -0,0 +1,20 @@ +# ADR-0005: PurchaseRequest as Bounded Auth Pool + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 1.1, 4.3, 5.6, 6 + +## Context + +Buyer agents need to call paid remote inference without giving runtime sidecars their signing keys. + +## Decision + +Treat `PurchaseRequest` as a bounded pool of pre-signed x402 authorizations. `buy.py` signs locally, embeds auths in the CR, and the controller writes sidecar config/auth material. It is not escrow. + +## Consequences + +- Positive: maximum buyer loss is bounded by signed auth count and price. +- Positive: x402-buyer has zero signer access. +- Negative: refill requires agent-managed signing, not controller automation. +- Neutral: status counters are reconciled snapshots; live state comes from sidecar `/status`. diff --git a/docs/adr/0006-erc8004-discovery-without-central-bazaar.md b/docs/adr/0006-erc8004-discovery-without-central-bazaar.md new file mode 100644 index 0000000..c0f8558 --- /dev/null +++ b/docs/adr/0006-erc8004-discovery-without-central-bazaar.md @@ -0,0 +1,20 @@ +# ADR-0006: ERC-8004 Discovery without Central Bazaar + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 1.1, 5.7 + +## Context + +Obol Stack competes with centralized service catalogs by publishing native standards metadata that any indexer can consume. + +## Decision + +Publish ERC-8004 registration JSON at `/.well-known/agent-registration.json`, track chain registrations in `AgentIdentity`, and let operators submit registration transactions through `obol sell register`. + +## Consequences + +- Positive: discovery is permissionless and indexer-friendly. +- Positive: controller avoids hidden custody or gas side effects. +- Negative: pending on-chain registration can lag behind an operationally ready route. +- Neutral: storefront and `/skill.md` show `registrationPending` rather than hiding usable services. diff --git a/docs/adr/0007-agent-crd-hermes-child-runtime.md b/docs/adr/0007-agent-crd-hermes-child-runtime.md new file mode 100644 index 0000000..2a5b878 --- /dev/null +++ b/docs/adr/0007-agent-crd-hermes-child-runtime.md @@ -0,0 +1,20 @@ +# ADR-0007: Agent CRD with Hermes Child Runtime + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 4.2, 5.3, 5.5, 6 + +## Context + +Providers need a real product primitive for durable child agents that can later be created by a permissioned mother agent. + +## Decision + +Add a namespaced `Agent` CRD for Hermes child runtimes. The CLI seeds host-side `soul.md` and skills; the controller provisions Hermes resources, optional remote-signer, status, and endpoint. `ServiceOffer type=agent` references this CR. + +## Consequences + +- Positive: "pay for an agent turn" becomes a first-class service type. +- Positive: model, skills, and runtime can be surfaced in x402 metadata. +- Negative: skills are seeds, not sandboxes. +- Neutral: in-cluster mother-agent creation remains a Phase 1 hardening path. diff --git a/docs/adr/0008-public-tunnel-allowlist.md b/docs/adr/0008-public-tunnel-allowlist.md new file mode 100644 index 0000000..e45fdc4 --- /dev/null +++ b/docs/adr/0008-public-tunnel-allowlist.md @@ -0,0 +1,20 @@ +# ADR-0008: Public Tunnel Allowlist + +**Date**: 2026-05-20 +**Status**: Accepted +**Impacts**: SPEC 3.2, 5.8, 6 + +## Context + +Cloudflare tunnel makes local services public. The stack must avoid accidentally publishing local dashboards, RPC, LiteLLM, or monitoring. + +## Decision + +Public tunnel traffic may reach only paid `/services/*`, `/skill.md`, `/api/services.json`, `/.well-known/agent-registration.json`, and the storefront root. Internal services remain hostname-restricted to `obol.stack`. + +## Consequences + +- Positive: seller endpoints and discovery are reachable by buyers. +- Positive: local control-plane surfaces stay private. +- Negative: every route template change must be reviewed as a security change. +- Neutral: quick tunnel URL churn is an operational concern handled by warnings and sync. diff --git a/features/agent_runtime.feature b/features/agent_runtime.feature new file mode 100644 index 0000000..92dc793 --- /dev/null +++ b/features/agent_runtime.feature @@ -0,0 +1,40 @@ +@bdd @phase0 +Feature: Agent CRD runtime + As a service provider + I want durable Hermes child agents declared as CRDs + So that I can sell or manage isolated agent services + + # References: SPEC Sections 4.2, 5.3; B&E B5-B8, E1-E2 + + Background: + Given the stack is running + And the Agent CRD is installed + + @integration + Scenario: Create a CRD-backed Hermes agent + When the operator runs "obol agent new quant --model qwen --skills buy-x402 --objective task --create-wallet" + Then namespace "agent-quant" exists + And host seed files exist under the agent Hermes home + And the Agent resource exists in namespace "agent-quant" + And the controller provisions Hermes runtime resources + + @fast + Scenario: Agent without a model does not become ready + Given an Agent has no spec.model and no status.pinnedModel + When the controller reconciles the Agent + Then condition "Provisioned" is "False" with reason "ModelUnpinned" + And condition "Ready" is "False" + + @integration + Scenario: Agent wallet creation is stable + Given an Agent has spec.wallet.create true + When the controller reconciles the Agent twice + Then one remote-signer keystore Secret exists + And status.walletAddress remains the same + + @fast + Scenario: Deleting an Agent waits for cleanup + Given an Agent has controller-managed child resources + When the Agent is deleted + Then the finalizer remains until child resources are removed + And the finalizer is removed after cleanup diff --git a/features/buy_side_payments.feature b/features/buy_side_payments.feature new file mode 100644 index 0000000..fe20fc6 --- /dev/null +++ b/features/buy_side_payments.feature @@ -0,0 +1,46 @@ +@bdd @phase0 +Feature: Buy-side x402 payments + As a buyer agent + I want to buy remote paid inference through bounded auth pools + So that I can call paid models without exposing my signer at runtime + + # References: SPEC Sections 4.3, 5.6; B&E B15-B19, B26, E8-E9 + + Background: + Given the stack is running + And the default Hermes agent has the buy-x402 skill + + @integration + Scenario: Buy command creates a ready PurchaseRequest + Given a seller endpoint returns HTTP 402 with matching token pricing + When the user runs "obol buy inference remote --seller URL --model qwen --budget 1" + Then buy.py creates a PurchaseRequest with pre-signed auths + And the controller writes buyer config and auth ConfigMaps + And LiteLLM exposes "paid/qwen" + + @integration + Scenario: Paid model request spends one auth + Given PurchaseRequest "remote" is Ready with remaining auths + When Hermes calls LiteLLM with model "paid/qwen" + Then x402-buyer attaches an X-PAYMENT header + And the seller response status and body are propagated + And sidecar status eventually reports one fewer remaining auth + + @fast + Scenario: Non-payment-gated endpoint is rejected + Given a PurchaseRequest endpoint returns HTTP 200 to the pricing probe + When the controller reconciles the PurchaseRequest + Then condition "Probed" is "False" with reason "NotPaymentGated" + + @fast + Scenario: Duplicate public model is rejected + Given PurchaseRequest "a" owns model "qwen" + When PurchaseRequest "b" also requests model "qwen" + Then PurchaseRequest "b" has condition "Configured" "False" with reason "DuplicateModel" + + @fast + Scenario: Token mismatch fails before signing + Given seller pricing requires OBOL + When the user buys with token "USDC" + Then the host preflight fails with an asset mismatch + And no new PurchaseRequest is created diff --git a/features/erc8004_discovery.feature b/features/erc8004_discovery.feature new file mode 100644 index 0000000..85862da --- /dev/null +++ b/features/erc8004_discovery.feature @@ -0,0 +1,47 @@ +@bdd @phase0 +Feature: ERC-8004 discovery + As a buyer or permissionless indexer + I want service providers to publish standard identity metadata + So that I can discover paid agents without a central registry service + + # References: SPEC Sections 4.4, 4.5, 5.7; B&E B20-B22, U6, E7 + + Background: + Given the stack is running + And an AgentIdentity exists at "x402/default" + + @integration + Scenario: Registration-enabled offer publishes a well-known document + Given a ServiceOffer has registration.enabled true + When the controller reconciles the offer + Then a RegistrationRequest exists + And "/.well-known/agent-registration.json" is served + And the document includes x402Support, active status, services, and registrations when known + + @manual + Scenario: External registration updates identity status + Given the operator submits an ERC-8004 registration transaction + When the controller observes the matching chain event + Then AgentIdentity status records the chain and agentId + And ServiceOffer status reflects the agentId + + @fast + Scenario: Controller does not mint registration transactions + When registration is enabled + Then the controller publishes the document + But the controller does not submit a chain transaction + + @fast + Scenario: Shared registration owner propagates status + Given two ServiceOffers share the default AgentIdentity + And one offer owns the RegistrationRequest + When the owner registration status changes + Then the non-owner offer reports shared registration status + + @integration + Scenario: Pending on-chain registration still appears in catalog + Given an offer is operationally ready + And condition "Registered" is "False" with reason "AwaitingExternalRegistration" + When "/api/services.json" is rendered + Then the offer is included + And "registrationPending" is true diff --git a/features/obol_payment_asset.feature b/features/obol_payment_asset.feature new file mode 100644 index 0000000..7ab536d --- /dev/null +++ b/features/obol_payment_asset.feature @@ -0,0 +1,37 @@ +@bdd @phase0 +Feature: OBOL payment asset + As a service provider + I want OBOL to be a first-class x402 payment token + So that agent services can be priced in OBOL without extra contracts + + # References: SPEC Sections 4.1, 5.4, 5.6, 6; B&E B25-B26, E10 + + Background: + Given the token registry supports USDC and OBOL + + @fast + Scenario: OBOL sell command writes explicit asset metadata + When a seller creates an OBOL-priced ServiceOffer + Then spec.payment.asset.symbol is "OBOL" + And spec.payment.asset.decimals is 18 + And spec.payment.asset.transferMethod is "permit2" + And EIP-712 name and version are set + + @integration + Scenario: OBOL 402 response is signable + Given an OBOL-priced ServiceOffer is RoutePublished + When a buyer probes the paid route + Then the 402 response advertises the OBOL contract address + And the x402 extensions include the Permit2 signing metadata + + @fast + Scenario: Unsupported OBOL chain is rejected + When the seller requests OBOL on an unsupported chain + Then token resolution fails + And no ServiceOffer is applied + + @fast + Scenario: Buyer cannot pay wrong token + Given seller pricing advertises OBOL + When the buyer requests token "USDC" + Then buy preflight rejects the request before signing diff --git a/features/release_smoke.feature b/features/release_smoke.feature new file mode 100644 index 0000000..5ae4a74 --- /dev/null +++ b/features/release_smoke.feature @@ -0,0 +1,37 @@ +@bdd @phase0 +Feature: Release smoke + As a maintainer + I want release flows to validate the real seller and buyer journeys + So that changes do not regress agent commerce primitives + + # References: SPEC Section 9; B&E G6 + + Background: + Given the repository is on a release candidate branch + + @fast + Scenario: Static validation passes + When maintainers run shell syntax checks and Go unit tests + Then "bash -n flows/*.sh" succeeds + And focused Go tests for CLI, controller, x402, buyer, ERC-8004, agent, and tunnel pass + + @integration + Scenario: USDC dual-stack flow passes + When flow-11 runs + Then Alice sells a paid service + And Bob discovers and buys it + And payment settlement and balance deltas are asserted + + @integration + Scenario: Live OBOL flow passes when enabled + Given live OBOL smoke env vars and funded wallets are available + When flow-14 or flow-15 runs + Then Bob pays Alice with OBOL on Base Sepolia + And the settlement transfer is verified + + @integration + Scenario: Agent ServiceOffer smoke passes + When flow-16 runs + Then an Agent CRD is declared + And the ServiceOffer type is "agent" + And the 402 metadata includes agent runtime fields diff --git a/features/sell_side_monetization.feature b/features/sell_side_monetization.feature new file mode 100644 index 0000000..2823c27 --- /dev/null +++ b/features/sell_side_monetization.feature @@ -0,0 +1,45 @@ +@bdd @phase0 +Feature: Sell-side monetization + As a service provider + I want to publish x402-gated services from ServiceOffers + So that buyers can pay per request without a centralized marketplace + + # References: SPEC Sections 4.1, 5.4, 5.5; B&E B9-B14, B25, E3, E5 + + Background: + Given the stack is running + And x402-verifier and serviceoffer-controller are installed + + @integration + Scenario: HTTP ServiceOffer converges to a paid route + Given an upstream Service responds healthy + When the seller creates a ServiceOffer for that upstream + Then conditions "ModelReady", "UpstreamHealthy", "PaymentGateReady", and "RoutePublished" become "True" + And the HTTPRoute points at the shared x402 gateway + + @integration + Scenario: Agent ServiceOffer advertises runtime metadata + Given Agent "quant" is Ready with model "qwen" and skill "buy-x402" + When the seller creates a ServiceOffer of type "agent" + Then status.agentResolution contains model "qwen" + And an unpaid request returns HTTP 402 + And the 402 accepts extra contains agentModel, agentSkills, and agentRuntime + + @fast + Scenario: Unhealthy upstream blocks route publication + Given a ServiceOffer points to an upstream that returns HTTP 500 + When the controller reconciles the offer + Then condition "UpstreamHealthy" is "False" + And condition "RoutePublished" is not "True" + + @fast + Scenario: Direct verifier access fails closed + When a request calls x402-verifier "/verify" without "X-Forwarded-Uri" + Then the verifier returns HTTP 403 + + @integration + Scenario: Paused offer leaves the public catalog + Given a ServiceOffer is operationally ready + When annotation "obol.org/paused" is set to "true" + Then route children are removed or disabled + And the offer is absent from "/api/services.json" diff --git a/features/stack_lifecycle.feature b/features/stack_lifecycle.feature new file mode 100644 index 0000000..248098a --- /dev/null +++ b/features/stack_lifecycle.feature @@ -0,0 +1,40 @@ +@bdd @phase0 +Feature: Stack lifecycle and model routing + As an operator + I want the local stack and model gateway to converge from CLI commands + So that agents can use stable local infrastructure + + # References: SPEC Sections 5.1, 5.2; B&E B1-B4 + + Background: + Given a host with Obol Stack prerequisites installed + + @fast + Scenario: Initialize a clean stack + When the operator runs "obol stack init" + Then the stack ID and backend choice are persisted + And embedded infrastructure defaults are copied + + @integration + Scenario: Start an initialized stack + Given the stack has been initialized + When the operator runs "obol stack up" + Then the selected Kubernetes backend is running + And kubeconfig is written + And base namespaces and CRDs are installed + And host DNS is configured best-effort + + @fast + Scenario: Configure and prefer a model + Given LiteLLM is reachable + When the operator configures a provider model + And the operator prefers that model + Then the LiteLLM config lists the model before lower-ranked entries + And Hermes sync uses the configured inventory + + @fast + Scenario: Concrete paid model can be selected + Given LiteLLM contains "paid/*" and "paid/remote-qwen" + When the default agent model is selected + Then "paid/*" is skipped + And "paid/remote-qwen" is eligible diff --git a/features/tunnel_storefront.feature b/features/tunnel_storefront.feature new file mode 100644 index 0000000..fd84e64 --- /dev/null +++ b/features/tunnel_storefront.feature @@ -0,0 +1,35 @@ +@bdd @phase0 +Feature: Tunnel and public storefront + As a service provider + I want paid services exposed through a constrained public tunnel + So that buyers can discover and access only the intended public surfaces + + # References: SPEC Sections 3.2, 5.8, 6; B&E B23-B24, U1, E6 + + Background: + Given the stack is running + + @integration + Scenario: Tunnel startup propagates public URL + When the operator starts or restarts the tunnel + Then the active tunnel URL is recorded + And "obol-stack-config" contains the tunnel URL + And public catalog surfaces use the new base URL + + @integration + Scenario: Storefront is created for the tunnel hostname + Given the tunnel has a public HTTPS URL + When CreateStorefront runs + Then a hostname-pinned HTTPRoute exists in namespace "traefik" + And the storefront reads services from "obol-skill-md.x402.svc" + + @fast + Scenario: Internal routes remain private + When public tunnel routes are rendered + Then frontend, eRPC, LiteLLM, and monitoring routes are not exposed without hostname restriction + + @manual + Scenario: Quick tunnel URL change warns operator + Given the stack uses a quick tunnel URL + When a destructive action would stop the cluster or tunnel + Then the operator is warned that registered URLs may break