@@ -10,28 +10,27 @@ This guide walks you through exposing a local LLM as a paid API endpoint using t
1010> [ !NOTE]
1111> ` --per-mtok ` is supported for inference pricing, but phase 1 still charges an
1212> approximate flat request price derived as ` perMTok / 1000 ` using a fixed
13- > ` 1000 tok/request ` assumption. Exact token metering is deferred to the
14- > follow-up ` x402-meter ` design described in
15- > [ ` docs/plans/per-token-metering.md ` ] ( ../plans/per-token-metering.md ) .
13+ > ` 1000 tok/request ` assumption. Exact token metering is not implemented yet.
1614
1715> [ !IMPORTANT]
18- > The monetize subsystem is alpha software on the ` feat/secure-enclave-inference ` branch .
16+ > The monetize subsystem is alpha software.
1917> If you encounter an issue, please open a
2018> [ GitHub issue] ( https://github.com/ObolNetwork/obol-stack/issues ) .
2119
2220> [ !IMPORTANT]
23- > The current implementation is event-driven. ` ServiceOffer ` is the source of truth, ` serviceoffer-controller ` owns reconciliation, ` RegistrationRequest ` isolates registration side effects, and ` x402-verifier ` derives live routes directly from published ServiceOffers.
24- > Older references below to the obol-agent reconcile loop, heartbeat polling, or direct ` x402-pricing ` route mutation are historical.
21+ > ` ServiceOffer ` is the source of truth. ` serviceoffer-controller ` owns
22+ > reconciliation, ` RegistrationRequest ` isolates registration side effects, and
23+ > ` x402-verifier ` derives live routes directly from published ServiceOffers.
2524
2625## System Overview
2726
2827```
2928SELLER (obol stack cluster)
3029
31- obol sell http --> ServiceOffer CR --> Agent reconciles:
30+ obol sell http --> ServiceOffer CR --> serviceoffer-controller reconciles:
3231 1. ModelReady (pull model in Ollama)
3332 2. UpstreamHealthy (health-check Ollama)
34- 3. PaymentGateReady (create x402 Middleware + pricing route )
33+ 3. PaymentGateReady (create x402 Middleware)
3534 4. RoutePublished (create HTTPRoute -> Traefik gateway)
3635 5. Registered (ERC-8004 on-chain, optional)
3736 6. Ready (all conditions True)
@@ -177,12 +176,12 @@ That stores both values in the pricing config:
177176- enforced phase-1 charge: ` price = 0.00125 USDC / request `
178177- approximation input: ` approxTokensPerRequest = 1000 `
179178
180- The agent automatically reconciles the offer through six stages:
179+ The controller automatically reconciles the offer through six stages:
181180
182181```
183- ModelReady [check] Agent checks /api/tags, model already cached
184- UpstreamHealthy [check] Agent health-checks ollama:11434
185- PaymentGateReady [check] Creates Middleware x402-my-qwen + adds pricing route
182+ ModelReady [check] Controller verifies the model is available
183+ UpstreamHealthy [check] Controller health-checks ollama:11434
184+ PaymentGateReady [check] Creates Middleware x402-my-qwen
186185RoutePublished [check] Creates HTTPRoute so-my-qwen -> ollama backend
187186Registered -- Skipped (--register not set)
188187Ready [check] All required conditions True
@@ -191,7 +190,7 @@ Ready [check] All required conditions True
191190Watch the progress:
192191
193192``` bash
194- # Check conditions (wait ~60s for agent heartbeat)
193+ # Check conditions
195194obol sell status my-qwen --namespace llm
196195
197196# Verify Kubernetes resources
@@ -534,7 +533,7 @@ obol sell status
534533
535534### Pausing
536535
537- Stop serving an offer without deleting it. This removes the pricing route so requests pass through without payment :
536+ Pause an offer without deleting it:
538537
539538``` bash
540539obol sell stop my-qwen --namespace llm
@@ -556,7 +555,6 @@ Deletion:
556555
557556- Removes the ServiceOffer CR
558557- Cascades Middleware and HTTPRoute via OwnerReferences
559- - Removes the pricing route from the x402 verifier
560558- Deactivates the ERC-8004 registration (sets ` active=false ` )
561559
562560Verify cleanup:
@@ -585,7 +583,7 @@ Traefik Gateway
585583 |
586584 --> ForwardAuth to x402-verifier.x402.svc:8080
587585 | |
588- | +-- Match request path against pricing routes
586+ | +-- Match request path against published ServiceOffers
589587 | +-- No match? Return 200 (allow, free route)
590588 | +-- Match + no payment header? Return 402 + requirements
591589 | +-- Match + payment header? Verify with facilitator
@@ -612,7 +610,7 @@ Traefik Gateway
612610 +--------+---------+
613611 |
614612 +----------v-----------+
615- | PaymentGateReady | (create Middleware + pricing route )
613+ | PaymentGateReady | (create Middleware)
616614 +----------+-----------+
617615 |
618616 +---------v----------+
@@ -630,67 +628,57 @@ Traefik Gateway
630628
631629### Kubernetes Resources per ServiceOffer
632630
633- When the agent reconciles a ServiceOffer named ` my-qwen ` in namespace ` llm ` :
631+ When ` serviceoffer-controller ` reconciles a ServiceOffer named ` my-qwen ` in namespace ` llm ` :
634632
635633| Resource | Kind | Namespace | Name |
636634| ----------| ------| -----------| ------|
637635| ServiceOffer | ` obol.org/v1alpha1 ` | ` llm ` | ` my-qwen ` |
638636| Middleware | ` traefik.io/v1alpha1 ` | ` llm ` | ` x402-my-qwen ` |
639637| HTTPRoute | ` gateway.networking.k8s.io/v1 ` | ` llm ` | ` so-my-qwen ` |
640- | ConfigMap patch | ` v1 ` | ` x402 ` | ` x402-pricing ` (route added) |
641638
642639The Middleware and HTTPRoute have ` ownerReferences ` pointing at the ServiceOffer, so they are garbage-collected on deletion.
643640
644641### Pricing Configuration
645642
646- The x402 verifier reads its config from the ` x402-pricing ` ConfigMap:
643+ The x402 verifier reads cluster-wide payment defaults from the
644+ ` x402-pricing ` ConfigMap:
647645
648646``` yaml
649647wallet : " 0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
650648chain : " base-sepolia"
651649facilitatorURL : " https://facilitator.x402.rs"
652650verifyOnly : false
653- routes :
654- - pattern : " /services/my-qwen/*"
655- price : " 0.001"
656- description : " my-qwen inference"
657- payTo : " 0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
658- network : " base-sepolia"
659651` ` `
660652
661- This configuration is used by the ` litellm-config` ConfigMap in the `llm` namespace, which LiteLLM reads for model_list configuration.
662-
663- Per-route `payTo` and `network` override the global values, enabling multiple ServiceOffers with different wallets or chains .
653+ Published offer routes are derived from ` ServiceOffer` resources rather than
654+ being maintained manually in this ConfigMap. Per-offer `payTo` and `network`
655+ can still override the cluster defaults .
664656
665657---
666658
667659# # Troubleshooting
668660
669- # ## Agent not reconciling
661+ # ## Offer not reconciling
670662
671- The agent reconciles on a heartbeat (~60 seconds). Check agent logs :
663+ Check ServiceOffer conditions and controller logs :
672664
673665` ` ` bash
674- obol kubectl logs -n openclaw-* -l app=openclaw --tail=50
666+ obol sell status my-qwen --namespace llm
667+ obol kubectl logs -n x402 -l app=serviceoffer-controller --tail=50
675668` ` `
676669
677670# ## x402 verifier returning 200 instead of 402
678671
679- The pricing route may not have been added, or was overwritten. Check the ConfigMap :
680-
681- ` ` ` bash
682- obol kubectl get cm x402-pricing -n x402 -o jsonpath='{.data.pricing\. yaml}'
683- ` ` `
684-
685- Ensure a route matching your path exists in the `routes` list. The verifier logs its route count at startup :
672+ The ServiceOffer may not be `Ready`, or the request path may not match the
673+ published offer. Check the offer and the resources it owns :
686674
687675` ` ` bash
676+ obol sell status my-qwen --namespace llm
677+ obol kubectl get middleware x402-my-qwen -n llm
678+ obol kubectl get httproute so-my-qwen -n llm
688679obol kubectl logs -n x402 -l app=x402-verifier --tail=10
689- # Look for: "routes: 1" (or however many you expect)
690680` ` `
691681
692- If routes are missing, the agent may not have reconciled yet (heartbeat is ~60s). You can also re-trigger reconciliation by deleting and re-creating the ServiceOffer.
693-
694682# ## Facilitator unreachable from cluster
695683
696684If using a self-hosted facilitator on the host, verify the k3d bridge :
@@ -788,7 +776,7 @@ Replace `openclaw-obol-agent` with your actual OpenClaw namespace if different.
788776| `obol sell http <name> --wallet ... --chain ... --per-request ... --upstream ... --port ...` | Create a ServiceOffer |
789777| `obol sell list` | List all ServiceOffers |
790778| `obol sell status <name> -n <ns>` | Show conditions for an offer |
791- | `obol sell stop <name> -n <ns>` | Pause an offer (remove pricing route) |
779+ | `obol sell stop <name> -n <ns>` | Pause an offer without deleting it |
792780| `obol sell delete <name> -n <ns>` | Delete an offer and cleanup |
793781| `obol sell status` | Show cluster pricing and registration |
794782| `obol sell register --private-key-file ...` | Register on ERC-8004 |
@@ -797,9 +785,10 @@ Replace `openclaw-obol-agent` with your actual OpenClaw namespace if different.
797785
798786| Resource | Namespace | Purpose |
799787|----------|-----------|---------|
800- | `x402-pricing` ConfigMap | `x402` | Pricing routes and wallet config |
788+ | `x402-pricing` ConfigMap | `x402` | Cluster-wide wallet, chain, and facilitator settings |
801789| `x402-secrets` Secret | `x402` | Wallet address |
802790| `x402-verifier` Deployment | `x402` | ForwardAuth payment verifier |
791+ | `serviceoffer-controller` Deployment | `x402` | Reconciles ServiceOffers into published resources |
803792| `serviceoffers.obol.org` CRD | (cluster) | ServiceOffer custom resource definition |
804793| `traefik-gateway` Gateway | `traefik` | Main ingress gateway |
805794
0 commit comments