# Cost model and architecture decisions

- **Status:** working business-plan model, not a vendor commitment
- **Last full price check:** 2026-09-20
- **Vercel AI SDK and gateway comparison checked:** 2026-10-19
- **Currency:** USD, before tax
- **Scope:** the exercise editor, API, persistent agent conversations, inference, and execution of user code. This is an operating-cost model; it excludes engineering labor and revenue.

This document follows the [implementation plan](PLAN.md) and should change whenever an architecture decision changes. Prices below are vendor list prices checked on the applicable date above. Scenario volumes and resource sizes are assumptions, not forecasts or measured usage. The totals are planning estimates, not quotes.

> Historical pricing snapshot: prices reflect the check dates above and have not been reverified for this documentation publication. Recheck vendor sources before making a purchase decision.

## Architecture decisions that drive cost

| ID | Decision and current status | Cost consequence | Revisit when |
| --- | --- | --- | --- |
| A1 | **Working choice:** Supabase Auth and Postgres store users, exercises, source revisions, agent conversations, messages, tool calls, and run records. | One database/auth bill; text history and retention determine disk growth. | Schema, retention policy, or measured DB size changes. |
| A2 | **Selected service layout; Render candidate:** a static site, Fastify API, and one continuously running TypeScript worker with independent agent-turn and code-run consumers. | Two paid compute instances in the Render planning baseline; splitting the worker later adds another instance. | Hosting choice or measured latency, load, or credential isolation warrants a change. |
| A3 | **Selected:** both user-triggered and agent-triggered code runs use `code_runs` and execute in Modal Sandboxes. | Each run consumes sandbox CPU and memory for its lifetime, including startup; retries and extra agent tool calls multiply runs. | Runtime limits and observed execution duration are known. |
| A4 | **Candidate:** OpenRouter routes inference; the default model remains undecided. | Tokens dominate variable cost for premium models. Tool calls can add model steps and context. OpenRouter also charges on credit purchases. | Default/fallback models and context policy are selected. |
| A5 | **Working choice:** save conversations per exercise and retain enough turn state to resume them. | Long conversations increase both Postgres storage and tokens sent on later turns unless context is summarized or truncated. | Conversation retention and context-compaction policy are selected. |
| A6 | **Deferred:** object storage is unnecessary while exercises contain one text file per language and run output is kept as bounded text. | No storage-bucket line in the baseline; attachments, artifacts, or large logs would add one. | Binary uploads or durable run artifacts are introduced. |
| A7 | **Selected first-release language:** write the API and worker entrypoints in TypeScript/Node, sharing contracts but deploying as separate processes. User Python and TypeScript still run only inside Modal Sandboxes. | No separate infrastructure line in the baseline; one language reduces integration and maintenance work, which this operating-cost model does not price. | A Python-only model/runtime dependency or measured worker resource limits justify another language. |
| A8 | **Selected agent harness:** Vercel AI SDK Core runs as an Apache-2.0 library inside the TypeScript worker. Vercel hosting and Vercel AI Gateway are not selected by this decision. | No mandatory license or hosted-service charge. Inference, gateway, worker compute, and storage remain separate cost lines. | The SDK lacks a needed capability, materially increases maintenance, or a managed agent runtime is selected. |

These IDs are cost references, not finalized API or agent-interface specifications.

### Selected agent harness (A8)

Vercel AI SDK Core is an embedded open source dependency, so selecting it adds **$0 incremental vendor cost** to the fixed baseline. The worker still pays for its existing compute, model tokens, database activity, and sandbox runs. The SDK does not require deployment on Vercel and does not make Vercel AI Gateway part of the architecture. [AI SDK repository and license](https://github.com/vercel/ai)

Gateway cost stays separate. If OpenRouter is selected, its current Standard plan charges a **5.5% fee when credits are purchased**. Vercel AI Gateway currently advertises **no token markup** and a **$5 monthly free credit until credits are purchased**. Only one gateway fee policy should be applied to a request; do not add both gateway costs to the same model usage. [OpenRouter pricing](https://openrouter.ai/pricing), [Vercel AI Gateway pricing](https://vercel.com/docs/ai-gateway/pricing)

### Proposed job delivery (A2/A3)

The first-release design uses **two durable Supabase Queues** backed by Postgres: `agent_turns` and `code_runs`. Both the API and the agent consumer create code-run jobs through the same database operation. A queue message contains only a job ID; the run row holds the immutable code snapshot, owner, limits, and status. Create the record and enqueue its message in one database transaction (for example, a database function called by the API) so neither can exist alone. Use a logged Basic Queue, acknowledge only after persisting the result, and make handlers idempotent because a crashed worker can cause redelivery after the visibility timeout. Set a maximum retry count. [Supabase Queues](https://supabase.com/docs/guides/queues), [queue API](https://supabase.com/docs/guides/queues/api)

The API authenticates, checks ownership and quotas, records the request, and returns a job ID. The worker uses TypeScript/Node in the same repository as the Fastify API, with a separate process entrypoint and deployment. The worker runs **two independent consumers with separate concurrency limits**. An agent turn can enqueue a code run and wait for its result while the run consumer remains free to process it. The worker holds Modal credentials; the API does not. In a later split, only the run worker would need those credentials. The API streams persisted output and status to clients via SSE; cancellation and event delivery details remain open. Each code job creates a Modal Sandbox, which is not another always-on Render service. [Render workers](https://render.com/docs/background-workers), [Modal Sandboxes](https://modal.com/docs/guide/sandboxes)

This keeps one paid worker instance in the planning baseline. Supabase does not list a separate Queues subscription on its [pricing page](https://supabase.com/pricing), but queue rows, polling, and writes consume database resources. Measure queue depth, claim latency, retries, and database load. If either consumer causes unacceptable delay or needs independent scaling or credentials, split them into two worker deployments and add at least one more paid instance to this model.

## Cost register

| Cost center | Current planning rate or allowance | Billing driver and decision | Baseline treatment |
| --- | --- | --- | --- |
| Database and auth | Supabase Pro starts at **$25/month**, including the first Micro project, 8 GB database disk, 100,000 monthly active users (MAU), 250 GB egress, and daily backups with 7-day retention. Above included amounts: disk **$0.125/GB**, MAU **$0.00325/MAU**, egress **$0.09/GB**. [Supabase pricing](https://supabase.com/pricing) | A1, A5: user count, source revisions, chat history, queue records, and outbound data. | **$25/month** for one production project within allowances. Free plan is useful for development but pauses after inactivity and has no automatic backups. |
| Web hosting | Render static sites are free to deploy. [Render pricing](https://render.com/pricing) | A2: static assets and bandwidth. | **$0 compute**, with bandwidth counted below. |
| API hosting | Render's smallest paid service is **$7/month** (0.5 CPU, 512 MB). [Render pricing](https://render.com/pricing) | A2: always-on Fastify API; scale by instance size/count. | **$7/month** for one instance. |
| Queue/agent worker | Same Render **$7/month** tier for a background worker. [Render pricing](https://render.com/pricing) | A2: independently consumes agent turns and code runs, with separate concurrency limits. | **$7/month** for one always-on worker. A second worker deployment would add at least **$7/month** at this tier. |
| Durable job queue | Supabase Queues stores messages in Postgres; no separate queue subscription appears on [Supabase pricing](https://supabase.com/pricing). [Queues guide](https://supabase.com/docs/guides/queues) | A2/A3: message count, retention, polling frequency, and retries add database work and storage. | No separate vendor line assumed while selected DB capacity and limits suffice. |
| Agent harness | Vercel AI SDK Core is open source under Apache 2.0 and runs inside the existing worker. [Repository](https://github.com/vercel/ai), [license](https://github.com/vercel/ai/blob/main/LICENSE) | A8: library upgrades, integration work, and worker resource usage; model and gateway charges are tracked separately. | **$0 incremental vendor cost**; no Vercel hosting or gateway subscription is required. |
| Code sandbox | Modal Sandbox CPU **$0.00003942 per physical core-second** and memory **$0.00000667 per GiB-second**. Billable usage is the greater of requested and actual resources. [Modal pricing](https://modal.com/pricing), [resource billing](https://modal.com/docs/guide/sandbox-resources) | A3: number of runs × lifetime × CPU/memory; bursts, retries, and idle time inside a sandbox matter. | Variable. Do not use ordinary Modal Function rates for Sandboxes. The Starter plan lists **$30/month of compute credit**; scenario totals show gross usage before credits. |
| Model inference | Example catalog rates: Qwen3 Coder Next **$0.12/M input** and **$0.80/M output** tokens; Claude Sonnet 4.6 **$3/M input** and **$15/M output** tokens. [Qwen pricing](https://openrouter.ai/qwen/qwen3-coder-next/api), [Sonnet pricing](https://openrouter.ai/anthropic/claude-sonnet-4.6/pricing) | A4, A5: all model steps per user turn, including tool-result context, retries, and fallback requests. | Variable; examples bracket a cheaper and a premium coding model, not a quality recommendation. Cache, reasoning, search, and provider-specific rates are excluded. |
| Model gateway | OpenRouter Standard charges **5.5% when credits are purchased**, with no monthly minimum; the fee is not charged per inference request. [OpenRouter pricing](https://openrouter.ai/pricing), [fee explanation](https://openrouter.ai/business) | A4: cash loaded to the account. | Track separately from token usage to avoid double counting or assuming purchase dates match consumption. |
| Auth email | Supabase's default SMTP is unsuitable for public production signups; a custom SMTP provider is required. [Supabase SMTP guide](https://supabase.com/docs/guides/auth/auth-smtp) Resend is a **candidate**, with a free tier of 3,000 emails/month and 100/day; Pro is **$20/month** for 50,000 emails, then $0.90/1,000. [Resend pricing](https://resend.com/pricing) | A1: signups, password resets, invitations, and provider choice. | **$0 provisional** only if a free provider meets volume and deliverability needs; paid tier is a launch decision. |
| Traffic and builds | Render Hobby lists **5 GB bandwidth/month**, then **$0.15/GB**, and **500 build minutes/month**, then **$5/1,000 minutes**. [Render pricing](https://render.com/pricing) | A2: website/API transfer and deploy frequency. | Assumed within allowances; measure before relying on $0. |
| Source control and CI | GitHub Free lists private repositories and **2,000 Actions minutes/month**. Overage depends on runner and storage. [GitHub pricing](https://github.com/pricing), [Actions billing](https://docs.github.com/en/billing/concepts/product-billing/github-actions) | Delivery workflow, team size, test load. | **$0 provisional** if the free plan is used and usage stays within allowance. |
| Recovery and observability | Supabase Pro includes daily backups; point-in-time recovery starts at **$100/month**. Supabase log drain is **$60/drain/month** plus event and egress charges. Render Hobby lists **7-day log retention**. [Supabase pricing](https://supabase.com/pricing), [Render pricing](https://render.com/pricing) | A1, A2: recovery objectives, log retention, alerting, and audit needs. | Optional upgrades excluded; decide recovery target before launch. |
| Domain and DNS | Registrar, TLD, renewal price, DNS provider, and number of domains are undecided. Render lists two custom domains included on Hobby, then **$0.25/domain/month**. [Render pricing](https://render.com/pricing) | Public launch and branded auth email. | **TBD**; get a registrar quote. |
| Staging and other environments | Another Supabase project on Pro starts from **$10/month**, and additional Render services/workers are billed separately. [Supabase pricing](https://supabase.com/pricing), [Render pricing](https://render.com/pricing) | Production separation and test workload. | Excluded until environment topology is chosen. |
| Payments, support, legal, taxes | No billing provider, support stack, or jurisdiction selected. | Business model and customer requirements. | **TBD**, outside the infrastructure subtotal; add before a full business-plan P&L. |

**Fixed planning subtotal: $39/month** = Supabase Pro $25 + Render API $7 + Render worker $7. This assumes one production project, one API instance, one worker, a free static site, and no paid workspace tier. It excludes the rows marked variable or TBD. It is not a guaranteed minimum bill: usage charges, credits, taxes, and vendor decisions can change it.

## Unit economics

For each **agent turn** (one user request plus all model steps and tool calls until the agent stops):

```text
model cost = Σ over model requests [
  (input tokens / 1,000,000 × input rate)
  + (output tokens / 1,000,000 × output rate)
  + any cache, reasoning, search, or other provider charges
]

sandbox cost per run ≈ seconds alive × [
  billable physical cores × $0.00003942
  + billable GiB of memory × $0.00000667
]

monthly operating cost ≈ fixed services + model usage + sandbox usage
  + email + bandwidth + storage/auth overages + monitoring + other selected services
```

Use actual billed CPU and memory, which may exceed the requested resources. The sandbox estimate assumes no GPU, paid volume, or special network charges. If a single sandbox is reused across runs, allocate its full lifetime across those runs; do not multiply a one-time startup by every command. Count each manual run as a sandbox run too. OpenRouter's credit-purchase fee is a separate cash-flow line; for a long-run estimate with all credits consumed, **5.5% of model spend** is a useful approximation, but it is not an extra per-request rate.

## Illustrative monthly scenarios

Assumptions solely for this table: each agent turn totals **12,000 input + 2,000 output tokens across all its model requests** and triggers **one** code run. Each manual run triggers one more code run. Every run lasts **10 seconds** at **0.5 physical core + 0.5 GiB** with no burst. Thus one run costs **$0.00023045**; one agent turn's model usage costs **$0.00304** on Qwen or **$0.066** on Sonnet. No cache discounts, failures, retries, overages, or free credits are applied. Actual agent turns may involve several model requests, much longer context, and multiple sandbox calls.

| Scenario | Agent turns / month | Manual code runs / month | Total sandbox runs | Sandbox gross | Qwen model | Total with Qwen | Sonnet model | Total with Sonnet |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Trial | 100 | 500 | 600 | $0.14 | $0.30 | **$39.44** | $6.60 | **$45.74** |
| Early use | 1,000 | 5,000 | 6,000 | $1.38 | $3.04 | **$43.42** | $66.00 | **$106.38** |
| Growth illustration | 10,000 | 50,000 | 60,000 | $13.83 | $30.40 | **$83.23** | $660.00 | **$712.83** |

Totals add the unrounded sandbox and model costs to the $39 fixed subtotal, then round to cents. They **exclude** the OpenRouter credit-purchase fee and all TBD lines. The growth row extrapolates unit costs only: it does not assert that the single small API, worker, or database can handle that workload. For a normalized cash-spend comparison, add about **$0.02 / $0.17 / $1.67** to the Qwen totals or **$0.36 / $3.63 / $36.30** to the Sonnet totals for OpenRouter credit purchases, assuming credits purchased equal model spend.

The largest unknowns are the **default model**, tokens accumulated during a resumed conversation, the **number of model steps and code runs per turn**, and how long sandboxes remain alive. A 10× increase in any one of those usage drivers can overwhelm the apparent precision of the table. Real invoices and telemetry should replace these examples.

## Decisions and measurements needed next

1. **Select model policy (A4/A5):** default model, fallback order, per-turn step/token/cost caps, context summarization, and whether cached tokens are expected. Store model ID and actual token/cost usage on each turn so monthly spend can be reconciled.
2. **Select sandbox limits (A3):** timeout, maximum cores/memory, concurrency, retry policy, and whether a sandbox can be reused. Measure p50/p95 lifetime and billed resources separately for Python, TypeScript, manual runs, and agent runs.
3. **Select queue and worker topology (A2/A3):** begin with a durable queue design that supports both run sources. If Postgres polling suffices, no extra queue product is needed; if a dedicated queue or another worker is chosen, add its price and operating overhead here.
4. **Set persistence policy (A1/A5/A6):** estimate bytes per exercise revision, message, tool result, and run output; set retention/deletion rules and a maximum stored output size. Monitor database size and egress monthly.
5. **Set launch requirements:** decide production SMTP, domain, backup recovery target, log retention, staging environment, and expected availability. Price these before calling the model a complete launch budget.

## Update procedure

- On every architecture decision, change its status above and update the corresponding cost-register row and fixed subtotal. Record the decision date and reason in the change log below; add a link to an ADR when one exists.
- Recheck the linked vendor pages at least quarterly and before a pricing or launch decision. Update the **last price check** date only for prices actually verified.
- Monthly after launch, capture vendor invoices/credits and application counters: MAU, database GB, egress GB, API/worker instance-hours, model requests/tokens/cost by model, sandbox runs/seconds/billed CPU and memory, email count, build minutes, and retries. Compare actuals with the scenario assumptions and replace assumptions with observed distributions.
- Report **gross usage, credits/discounts, and cash paid separately**. Keep one-off setup purchases and recurring operating expenses separate. Do not net free credits against unit economics.

## Change log

| Date | Change | Architecture link |
| --- | --- | --- |
| 2026-09-20 | Initial price register and illustrative scenarios; all deployment sizes and model choices remain planning assumptions. | A1–A6 |
| 2026-09-20 | Added proposed Supabase Queues delivery and one-worker/two-consumer topology; no deployment decision finalized. | A2/A3 |
| 2026-09-20 | Added TypeScript/Node as the proposed worker runtime; no infrastructure-price change. | A7 |
| 2026-09-20 | Confirmed the first-release TypeScript worker, two-consumer queue layout, and Modal Sandboxes. Render and OpenRouter/model choices remain candidates. | A2/A7 |
| 2026-10-19 | Selected Vercel AI SDK Core as the in-worker agent harness. This adds no mandatory hosted-service cost and does not select Vercel hosting or AI Gateway; OpenRouter remains the gateway candidate. | A8 |

## Documentation hosting preparation

The documentation deployment is separate from the application hosting model.
Cloudflare Pages is intended to build `docs/` plus shared `styles/` using
`node scripts/build-docs.mjs` and serve generated `dist-docs/`. The project `train-online-docs` is deployed at
https://train-online-docs.pages.dev/ . No paid service was enabled during setup,
and this deployment introduces no new price assumption. Watch paths are saved for docs, shared styles, and the build script.
A push-triggered update was verified with commit `11fdcf3`; Cloudflare
reported success and the homepage reflected the committed test text.
