AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
클라우드플레어 AI 게이트웨이와 함께하는 공개 멀티모달 AI 놀이터, 무료 AGPL 소프트웨어
Public signup multimodal AI playground. Bring your own Cloudflare AI Gateway. Chat, voice, image, video, music, RAG. Free AGPL software from Skyphusion Labs.
개발 도구TypeScriptGitHub @skyphusion-labs온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Multi-tenant control plane for commercial Prism (AGPL-3.0).
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
License: AGPL-3.0-only
Sibling: prism (playground + inference Worker)
Native clients: prism-ios, prism-android (build against this plane's contract)
The metering and policy plane for commercial Prism: accounts, plan entitlements, a usage ledger,
and a metered proxy to Cloudflare AI Gateway / Workers AI. Cost-recovery economics (cover CF and
inference expense, not extractive margins). Full stack stays AGPL, so a self-hoster can run the same
machinery on their own Cloudflare account.README 전문 보기 ↗License: AGPL-3.0-only
Sibling: prism (playground + inference Worker)
Native clients: prism-ios, prism-android (build against this plane's contract)
The metering and policy plane for commercial Prism: accounts, plan entitlements, a usage ledger, and a metered proxy to Cloudflare AI Gateway / Workers AI. Cost-recovery economics (cover CF and inference expense, not extractive margins). Full stack stays AGPL, so a self-hoster can run the same machinery on their own Cloudflare account.
This plane owns who may call what, and how much. Conversation history, RAG, artifacts, and the multimodal surface stay in prism.
Live at play-proxy.skyphusion.org, AI Gateway prism-proxy. Full production wiring and credential
model: docs/ARCHITECTURE.md.
flowchart TB
subgraph clients["Clients (docs/CONTRACT.md + openapi.yaml)"]
ios["prism-ios<br/>Prism for iOS"]
android["prism-android"]
other["Any Bearer client"]
end
subgraph plane["Worker: prism-control-plane<br/>play-proxy.skyphusion.org"]
direction TB
enroll["POST /v1/clients<br/>enroll → pcp_ key"]
me["GET /v1/me · /v1/models · /v1/usage"]
doors["Metered doors<br/>chat · images · video · speech<br/>transcriptions · music · STT stream"]
jobs["GET /v1/jobs/:id<br/>PlaneLongRunWorkflow"]
store["POST /v1/store/redeem<br/>App Store / Play credit"]
media["MEDIA R2<br/>/v1/media/* signed URLs"]
admin["/admin/* (ADMIN_TOKEN)"]
end
subgraph d1["D1"]
acct["accounts · clients · plans"]
ledger["usage_events · credit_grants<br/>async_jobs"]
end
subgraph upstream["Cloudflare AI"]
gw["AI Gateway: prism-proxy"]
wai["Workers AI @cf/*"]
ub["Unified Billing providers"]
end
subgraph stores["App stores"]
as["App Store IAP JWS"]
gp["Google Play purchase token"]
end
ios --> enroll
ios --> me
ios --> doors
ios --> jobs
ios --> store
ios --> media
android --> doors
other --> doors
as --> store
gp --> store
admin --> plane
enroll --> acct
doors --> ledger
store --> acct
jobs --> ledger
jobs --> media
doors --> gw
jobs --> gw
gw --> wai
gw --> ub
sequenceDiagram
participant C as Client
participant P as control plane
participant D as D1 ledger
participant G as AI Gateway
participant M as Model
C->>P: Bearer pcp_ + POST door
P->>D: identity, plan, rate limit
P->>D: balance gate (402 if exhausted)
P->>G: AI.run / REST (no prompt stored)
G->>M: inference
M-->>G: result
G-->>P: body + gateway log id
P->>D: usage_events micro-USD
P-->>C: JSON 200 (+ money headers)
sequenceDiagram
participant C as Client
participant P as control plane
participant W as PlaneLongRunWorkflow
participant G as AI Gateway
participant R as MEDIA R2
C->>P: Prefer: respond-async / async:true
P->>P: create async_jobs row
P->>W: LONGRUN.create
P-->>C: 202 + job id
W->>G: blocking env.AI.run
G-->>W: asset
W->>R: rehost when needed
W->>P: job succeeded + result_json
C->>P: GET /v1/jobs/:id poll
P-->>C: result URLs / data
Catalog: 93 models in src/catalog.ts (same set as playground src/models.ts). Live list:
GET /v1/models.
Native clients (prism-ios, prism-android) and any future SDK are written against a contract defined here, not against whatever the Worker happens to return today:
docs/CONTRACT.md -- the normative contract. Auth, enrollment, error codes
and their retry semantics, quota behaviour, limits, and the open decisions still owed.docs/openapi.yaml -- the same surface, machine-readable.tests/contract.test.ts fails the build if the router and the contract drift apart.
Conversation history and compact live on the playground Worker (prism), not here. This plane never stores prompts or completions.
Prompt and completion text is never persisted by this plane. The ledger stores counts (tokens,
micro-USD, model id, status); there is no column that can hold message content, and
tests/schema-privacy.test.ts enforces that against migrations/. At the gateway the two logging
switches are separate: the payload switch is hard-wired off on every call that reaches the
gateway, so prompt and completion bodies are dropped, while the metadata row defaults ON
(AI_GATEWAY_COLLECT_LOG, src/env.ts) and holds token counts, model, provider, status, cost and
duration. That row is what lets Cloudflare's own per-request cost be reconciled against our ledger;
it carries no content. Any change to that is a contract change. See docs/CONTRACT.md for the full
posture, including what this plane does retain and for how long.
The consequence, stated plainly: this plane cannot replay or audit a conversation. Clients hold the only copy of what they sent.
The table below is the core client surface only (health, enroll, me, models, usage, chat). The full
non-chat surface (images, speech, transcriptions, videos, music, STT sessions) and every error code is
normative in docs/CONTRACT.md and docs/openapi.yaml. Prefer
those files when wiring a client.
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness. Touches no binding. |
| GET | /health/deep | Readiness: D1 schema applied, catalog priced, gateway configured. |
| POST | /v1/clients | Enroll a device against a one-time token, receive a client key. |
| GET | /v1/me | Account, plan, entitlements, current usage. |
| GET | /v1/models | Entitlement-filtered model list with published prices. |
| GET | /v1/usage | Current-period usage, including the unmetered count. |
| POST | /v1/chat/completions | Metered inference, OpenAI-compatible. |
Operator routes (/admin/*, single bearer, 503 when ADMIN_TOKEN is unset) create accounts, mint
enrollment tokens, and revoke client keys. They are not part of the client contract.
93 priced models in src/catalog.ts (mirrored by playground src/models.ts). Entitlement-filtered at
GET /v1/models. Full id tables live in the prism-ios README
and prism README Features section.
| Modality | Count |
|---|---|
| Chat | 44 |
| Image | 21 |
| Video | 19 |
| TTS | 3 |
| STT | 4 |
| Music | 1 |
| Live voice (Flux) | 1 |
| Total | 93 |
max_output_tokens. The bound
is documented rather than hidden; a pre-flight gate on a post-hoc cost cannot be exact. 402 when the
balance is gone.YYYY-MM. It groups usage for display; it
does not reset the balance and it grants nothing.src/catalog.ts. Cloudflare's own per-request cost
figure is read back later, by POST /admin/reconcile, and used only to true up the estimate as an
auditable adjustment; it never gates or delays a response. See docs/ARCHITECTURE.md#pricing-and-why-it-needs-reconciliation.plans.monthly_included_micro_usd is spent first each
UTC month; unused expires (never becomes a cash grant). Zero means pure prepaid. Credit is lifetime.metered = 0 and a reason, charges
nothing, and increments a separate unmetered_requests counter that GET /v1/usage publishes.
"Nothing was owed" and "we could not tell what was owed" are different facts, and collapsing them
would turn every gap in the meter into a silent free ride.npm ci
npm run typecheck # tsc on src and tests -- CI gate
npm test # vitest run: node suite + workers DO harness
npm run test:node # FakeStore suite only (no workerd)
npm run test:workers # STT Durable Object harness (local workerd, no live upstream)
npm run bootstrap # wrangler.example.toml -> wrangler.toml (gitignored)
npx wrangler d1 create prism-control-plane # paste the id into wrangler.toml
npm run db:migrate:local
npm run dev
Then drive it end to end. ADMIN_TOKEN and any other secrets go in .dev.vars (gitignored):
# 1. an account on the seeded provisional plan
curl -sX POST localhost:8787/admin/accounts \
-H "authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \
-d '{"plan_id":"dev"}'
# 2. a one-time enrollment token for it
curl -sX POST localhost:8787/admin/enrollments \
-H "authorization: Bearer $ADMIN_TOKEN" -H 'content-type: application/json' \
-d '{"account_id":"acct_..."}'
# 3. trade it for a client key (shown once)
curl -sX POST localhost:8787/v1/clients \
-H 'content-type: application/json' \
-d '{"enrollment_token":"...","platform":"other"}'
# 4. call a model (needs AI_GATEWAY_ID set, or this answers 503 by design)
curl -si localhost:8787/v1/chat/completions \
-H "authorization: Bearer pcp_..." -H 'content-type: application/json' \
-d '{"model":"@cf/meta/llama-3.2-3b-instruct","messages":[{"role":"user","content":"hi"}]}'
Step 4 spends real Workers AI inference. Steps 1 to 3 do not.
The whole request path is testable without workerd: persistence sits behind ControlPlaneStore and
the upstream behind InferenceRunner, so tests/router.test.ts drives every gate with an in-memory
store and a fake runner.
Deployed and live at play-proxy.skyphusion.org. Built: the client contract, dual-pool metering
(monthly allowance then prepaid credit), shared-token AI Gateway proxy with cf-aig-metadata
attribution, priced catalog (45/44 chat), SSE metering, operator reconciliation
(POST /admin/reconcile), and operator plan upsert (POST /admin/plans). Provisional dev plan
covers standard+premium chat. Non-chat unit-metered doors and Flux live STT are built. Catalog rate
refresh: POST /admin/catalog/refresh (operator; dry-run default) pulls chat rates from AI Gateway
compat/models into model_prices. Reconcile cron: hourly scheduled dry-run (live only with
RECONCILE_CRON_LIVE=true). Not built: store-receipt enrollment, commercial plan pricing. No
overage billing, ever.
Full production wiring, the mermaid flowchart, and the reconciliation design are in
docs/ARCHITECTURE.md. Agent-facing guidance, non-negotiables, and deploy /
secret procedure are in CLAUDE.md.
Plan pricing, the model set, and enrollment source of truth are open decisions listed at the end of
docs/CONTRACT.md. The seeded dev plan is a provisional
placeholder for local work, not a product tier.
play-proxy.skyphusion.org