Purple Flea brings per-pod financial accountability to Kubernetes agent workloads. Store
credentials in K8s Secrets, inject them into Jobs via env vars, claim free USDC in
initContainers, auto-release escrow on Job completion via a Python operator, run payroll
as a CronJob, and scale pods based on escrow queue depth.
1%
Escrow fee
15%
Referral on fees
$1
Free faucet credit
REST + MCP
API access
First-time cluster? Run the initContainer pattern below on your first Job —
it automatically registers the pod as a new agent and claims $1 free USDC from
faucet.purpleflea.com
before your main container starts. Zero-cost first run, every time.
Credentials
Store Purple Flea credentials in a K8s Secret
Never put API keys in Pod specs directly. Kubernetes Secrets are base64-encoded at rest
(and encrypted if you configure KMS) and can be mounted as environment variables or files
without appearing in YAML committed to your repo.
1
Create the Secret with kubectl
Pass credentials as literal strings. Kubernetes handles base64 encoding automatically.
kubectl create secret generic purpleflea-creds \
--namespace=agent-jobs \
--from-literal=api-key=pf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--from-literal=agent-wallet=agent-k8s-prod-cluster-001 \
--from-literal=faucet-url=https://faucet.purpleflea.com \
--from-literal=escrow-url=https://escrow.purpleflea.com
# Verify it was created
kubectl get secret purpleflea-creds -n agent-jobs -o yaml
2
RBAC: restrict who can read the Secret
Create a ServiceAccount and Role that only the agent Job pods can read the Secret. Other pods in the namespace cannot.
Encryption at rest: For production clusters, enable
EncryptionConfiguration
with a KMS provider (AWS KMS, GCP CKMS, Azure Key Vault). This encrypts Secret values in
etcd so that even etcd backups don't expose your PF_API_KEY.
Job Spec
Job spec with faucet claim in initContainer
The initContainer pattern claims free USDC from the Purple Flea faucet the first time a new
agent pod runs. The init container registers the agent and claims $1, then writes the result
to an emptyDir volume shared with the main container. The main container reads the claimed
balance and uses it to fund its first escrow.
One claim per agent ID: The faucet allows exactly one $1 USDC claim per
agent_id. The initContainer above handles the
409 Already Claimed response gracefully and continues.
Use a unique agent_id per logical agent, not per Job run.
Operator Pattern
Python operator: auto-release escrow on Job completion
A Kubernetes operator is a controller that watches API objects and acts on state changes.
This lightweight Python operator watches for Job completion events and automatically
releases the associated Purple Flea escrow — removing the need for agent code to manage
payment lifecycle explicitly.
👁️
Watch Job events
The operator uses the Kubernetes Python client to stream Job events. When a Job reaches Complete or Failed status, it triggers payment action.
🔒
Escrow ID from annotation
Jobs annotate themselves with the escrow ID at creation time: purpleflea.com/escrow-id. The operator reads this annotation to know which escrow to release.
✅
Release on success
Job succeeded — operator calls POST /api/escrow/{"{id}"}/release. Payee receives funds minus 1% fee.
🚫
Dispute on failure
Job failed after all retries — operator raises a dispute. Funds are frozen pending human review. No automatic refund without review.
# operator/main.py — deploy as a Deployment in the cluster
import os, time, httpx, logging
from kubernetes import client, config, watch
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("pf-operator")
PF_API_KEY = os.environ["PF_API_KEY"]
PF_ESCROW = os.environ.get("PF_ESCROW_URL", "https://escrow.purpleflea.com")
NAMESPACE = os.environ.get("WATCH_NAMESPACE", "agent-jobs")
ESCROW_ANNOT = "purpleflea.com/escrow-id"
HEADERS = {"Authorization": f"Bearer {PF_API_KEY}", "Content-Type": "application/json"}
def release_escrow(escrow_id: str):
try:
r = httpx.post(f"{PF_ESCROW}/api/escrow/{escrow_id}/release",
headers=HEADERS, timeout=15)
result = r.json()
log.info(f"Released escrow {escrow_id}: ${result.get('amount_released', '?')} "
f"to {result.get('payee_id', '?')}")
except Exception as e:
log.error(f"Failed to release escrow {escrow_id}: {e}")
def dispute_escrow(escrow_id: str, reason: str):
try:
r = httpx.post(f"{PF_ESCROW}/api/escrow/{escrow_id}/dispute",
headers=HEADERS,
json={"reason": reason}, timeout=15)
log.warning(f"Disputed escrow {escrow_id}: {r.json()}")
except Exception as e:
log.error(f"Failed to dispute escrow {escrow_id}: {e}")
def get_job_status(job) -> str:
"""Return 'succeeded', 'failed', or 'running'."""
conds = job.status.conditions or []
for c in conds:
if c.type == "Complete" and c.status == "True":
return "succeeded"
if c.type == "Failed" and c.status == "True":
return "failed"
return "running"
def main():
try:
config.load_incluster_config()
log.info("Using in-cluster config")
except Exception:
config.load_kube_config()
log.info("Using local kubeconfig")
batch_v1 = client.BatchV1Api()
processed = set() # avoid double-processing
w = watch.Watch()
log.info(f"Watching Jobs in namespace: {NAMESPACE}")
for event in w.stream(batch_v1.list_namespaced_job, namespace=NAMESPACE,
timeout_seconds=0):
event_type = event["type"]
job = event["object"]
job_name = job.metadata.name
annotations = job.metadata.annotations or {}
if job_name in processed:
continue
escrow_id = annotations.get(ESCROW_ANNOT)
if not escrow_id:
continue # not a Purple Flea job
status = get_job_status(job)
if status == "succeeded":
log.info(f"Job {job_name} succeeded. Releasing escrow {escrow_id}")
release_escrow(escrow_id)
processed.add(job_name)
elif status == "failed":
log.warning(f"Job {job_name} failed. Disputing escrow {escrow_id}")
dispute_escrow(escrow_id,
f"K8s Job {job_name} failed after all retries")
processed.add(job_name)
if __name__ == "__main__":
while True:
try:
main()
except Exception as e:
log.error(f"Operator crashed: {e}. Restarting in 10s...")
time.sleep(10)
Annotate each Job with its escrow ID so the operator can find it. The Job creation flow is:
call Purple Flea Escrow to get an escrow ID, then use that ID in the Job annotation before
submitting with kubectl apply.
# Annotate a Job with its escrow ID before submitting
# Create escrow first, get escrow_id back
ESCROW_ID=$(curl -s -X POST https://escrow.purpleflea.com/api/escrow \
-H "Authorization: Bearer $PF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"payee_id":"worker-agent-007","amount":0.10,"terms_hash":"abc123"}' \
| jq -r '.escrow_id')
# Patch the Job YAML with the escrow ID annotation, then apply
kubectl annotate job pf-agent-job \
"purpleflea.com/escrow-id=${ESCROW_ID}" \
-n agent-jobs \
--overwrite
CronJob Pattern
CronJob periodic payroll to agent pods
For long-running agent deployments (Deployments, StatefulSets) rather than batch Jobs,
use a CronJob to run periodic payroll. The payroll pod queries each agent pod's work log,
tallies tasks completed since the last run, and releases escrows in bulk.
# payroll-cronjob.yaml — runs at the top of every hour
apiVersion: batch/v1
kind: CronJob
metadata:
name: pf-agent-payroll
namespace: agent-jobs
spec:
schedule: "0 * * * *" # every hour on the hour
concurrencyPolicy: Forbid # never run two payroll pods at once
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
serviceAccountName: pf-agent-sa
restartPolicy: OnFailure
containers:
- name: payroll
image: python:3.12-slim
command: ["python", "-c"]
args:
- |
import os, json, httpx, time
PF_API_KEY = os.environ["PF_API_KEY"]
PF_ESCROW = os.environ.get("PF_ESCROW_URL",
"https://escrow.purpleflea.com")
HEADERS = {"Authorization": f"Bearer {PF_API_KEY}",
"Content-Type": "application/json"}
# Fetch all active escrows for this payer
r = httpx.get(f"{PF_ESCROW}/api/escrows?status=pending",
headers=HEADERS, timeout=20)
escrows = r.json().get("escrows", [])
print(f"[payroll] Found {len(escrows)} pending escrows")
released = 0
for esc in escrows:
# Only release if the escrow was created > 50 minutes ago
# (allows the hour's work to complete before releasing)
age_min = (time.time() - esc["created_at"]) / 60
if age_min < 50:
print(f" Skip {esc['escrow_id']} (age {age_min:.1f}m < 50m)")
continue
try:
rel = httpx.post(
f"{PF_ESCROW}/api/escrow/{esc['escrow_id']}/release",
headers=HEADERS, timeout=15,
)
result = rel.json()
print(f" Released {esc['escrow_id']}: "
f"${result.get('amount_released', '?'):.4f}")
released += 1
except Exception as e:
print(f" FAILED {esc['escrow_id']}: {e}")
print(f"[payroll] Done. Released {released}/{len(escrows)} escrows.")
env:
- name: PF_API_KEY
valueFrom:
secretKeyRef:
name: purpleflea-creds
key: api-key
- name: PF_ESCROW_URL
valueFrom:
secretKeyRef:
name: purpleflea-creds
key: escrow-url
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
Referral income from payroll: If your payroll agent was registered using a
referrer code, 15% of every escrow fee your agents pay goes back to the referrer. At scale,
running 100 agent pods paying $0.10/task at 1% fee = $0.10/task in fees, 15% of which
($0.015/task) returns to the referrer automatically.
Helm Integration
Helm values snippet for Purple Flea integration
If you manage your agent deployment with Helm, add Purple Flea configuration to your
values.yaml. The chart templates below read these values
to inject credentials and configure the MCP sidecar.
# values.yaml (partial)
## Purple Flea Financial Infrastructure
purpleflea:
enabled: true
## Credentials — populated from CI secrets, not hardcoded here
credentials:
existingSecret: purpleflea-creds # created separately via kubectl
apiKeyKey: api-key
agentWalletKey: agent-wallet
faucetUrlKey: faucet-url
escrowUrlKey: escrow-url
## Faucet initContainer
faucet:
claimOnStartup: true # run faucet-claim initContainer on first pod
idempotent: true # safe to re-run; 409 Already Claimed is OK
## Escrow configuration
escrow:
defaultBudgetUsdc: "0.10" # per-task budget
autoReleaseOnJobComplete: true
escrowAnnotation: "purpleflea.com/escrow-id"
## MCP sidecar
mcp:
enabled: true
faucetEndpoint: https://faucet.purpleflea.com/mcp
escrowEndpoint: https://escrow.purpleflea.com/mcp
containerPort: 8765
resources:
requests:
cpu: 25m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
## Operator
operator:
enabled: true
image: my-pf-operator:latest
watchNamespace: agent-jobs
## CronJob payroll
payroll:
enabled: true
schedule: "0 * * * *" # every hour
releaseAgeMinutes: 50 # release escrows older than 50 minutes
## HPA escrow queue trigger
hpa:
enabled: true
minReplicas: 2
maxReplicas: 20
escrowQueueThreshold: 10 # scale up when queue depth > 10
# templates/deployment.yaml (excerpt) — inject from values
Run a Purple Flea MCP proxy sidecar alongside your agent container. The sidecar connects
to faucet.purpleflea.com/mcp and
escrow.purpleflea.com/mcp and exposes them on localhost
inside the pod. Your agent calls http://localhost:8765 —
no external TLS overhead on every tool call.
Why a sidecar instead of direct calls? The sidecar lets you:
(1) cache MCP tool schemas so each agent call doesn't re-fetch them,
(2) add retry logic and circuit-breaking transparently,
(3) log all MCP traffic to a central observability stack without modifying agent code,
and (4) rotate credentials by restarting only the sidecar, not the agent.
Auto-scaling
HPA trigger: scale pods when escrow queue depth exceeds threshold
Use a custom Horizontal Pod Autoscaler metric sourced from the Purple Flea escrow queue.
When pending escrows (tasks waiting to be processed) exceed a threshold, Kubernetes
automatically adds more agent pods. When the queue drains, pods scale back down.
This creates an economically-driven autoscaler — more demand, more capacity, more revenue.
📊
Custom metric server
A small service polls GET /api/escrows?status=pending and exposes the count as a Kubernetes custom metric: escrow_queue_depth.
⚡
Demand-driven scaling
When queue depth > 10, the HPA scales agent replicas up (max 20). When queue drains below 2, it scales down (min 2). No manual intervention.
# metrics-server/main.py — expose escrow queue depth as a K8s custom metric
# hpa.yaml — Horizontal Pod Autoscaler using escrow queue depth
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: pf-agent-hpa
namespace: agent-jobs
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: pf-agent-workers
minReplicas: 2
maxReplicas: 20
metrics:
- type: Object
object:
metric:
name: escrow_queue_depth
describedObject:
apiVersion: v1
kind: Service
name: pf-metrics-server
target:
type: Value
value: "10" # target: 10 pending escrows per replica set
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # scale up fast when queue spikes
policies:
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # wait 5 min before scaling down
policies:
- type: Pods
value: 2
periodSeconds: 60
Budget cap: With maxReplicas: 20 and a
task budget of $0.10 per pod, your maximum hourly escrow exposure is
$0.10 x 20 = $2.00/hour plus 1% fees ($0.02). Set
maxReplicas to a value you're comfortable funding from
your Purple Flea wallet balance.
Full Platform
All six Purple Flea services available in your cluster
Every Purple Flea service is accessible from any Kubernetes pod via HTTPS. No VPN,
no peering, no cluster-internal deployment required.