Trial agent ceiling in editor

Task 8939 · Done · Coder · created · updated

## Original requirement (verbatim, epic 8925) > When a new customer is on trial, they signed up for some agents, but they are not paying for them for 14 days. Let's give them unlimited agents and keep a tally of what was the max until now compared to what they signed up for. Something like 10/3. > > This needs to be web researched and analyzed and graphically represented in the sidebar with an unobtrusive Upgrade button that allows to increase the number of agents workin. Settled inputs: the BA PRD (Task 8923, decisions D1–D7, assumptions AS1–AS6, criteria 1–10) and the Architecture (Task 8924, §3 components, §4 decomposition). This child is one row of that §4 table. The contracts quoted below are pinned; do not re-design them. ## Settled decisions this child relies on (verbatim from the PRD) - **D1. The "max until now" number** is peak **demand** since the trial started: the maximum over time of **agents running + ready tasks the dispatcher held back only because of the org's agent cap** (the trial ceiling). Tasks waiting only for node or worktree capacity are not counted. The operator asked for "the maximum agents that were willing to run, but were limited by plan". - **D2. "Unlimited" during the trial** means the runner-slot cap is lifted to the plan's **Max agents**. When the plan has no Max agents, a new operator-set per-plan **trial agent ceiling** applies. A blank ceiling falls back to a fixed default of 10. Node and worktree capacity still bound the count. - **D3. Upgrade during a trial** raises the agent count without charging now. The new count is billed when the trial converts. The count field is prefilled with the peak. This lifts the `IN_TRIAL` refusal for the agent count only. Plan, interval and hosting changes stay refused during a trial. (Architecture amendment Q1: the trial agent change moves the count **both ways**, within the plan's min/max.) - **D4. The meter shows only while the subscription is `trialing`.** It is hidden after conversion, cancellation or expiry. - **D5. Audience.** Every member of the org sees the meter. Only Owners and Admins (`isOrgAdminSeat`) see the Upgrade button. - **D6. Trial end.** At conversion the cap returns to the paid agent count. Running work is never killed; only new dispatch is limited. In the last 3 days of the trial, if peak is greater than signed-up, the meter turns warning-coloured and states the gap, e.g. "Trial ends in 2d — used 10, paying for 3". - **D7. Scope** covers **per-agent trial plans only**. Flat plans have no customer-chosen "signed up for" number and are unchanged. - **AS1.** An org override (`orgs.limit_runner_slots`) still wins over the trial ceiling. **AS2.** The `active_worktrees` cap must not hold trial dispatch below the trial ceiling. **AS3.** A staging org shares its parent's ledger; its demand counts toward the parent's meter and the meter shows on both. **AS5.** Peak is a high-water mark persisted per org and per trial, reset when a new trial starts. ## This child: C2 — Trial agent ceiling in the plan editor Approach (Architecture §3.8): `apps/api/app/routes/billing_plans.py` create/update models gain `trial_agent_ceiling: Optional[int] = Field(None, ge=1, le=MAX_AGENTS)` (the bound its sibling `max_agents` uses), serialised beside `max_agents`; explicit `null` clears. The plans list response carries `trial_agent_ceiling_default` read from `plan_terms.TRIAL_AGENT_CEILING_DEFAULT` (C1's single home of 10). Web: `apps/web/src/pages/products/plan/planForm.ts` + `PlanFields.tsx` field `Trial agent ceiling` (testid `plan-field-trial-agent-ceiling`), placeholder from `trial_agent_ceiling_default` — never a second literal `10` — shown only when the plan is per-agent **and** trial days > 0 (AS6). Copy per `docs/standards/ui-copy-standards.md`; layout per `docs/standards/ui-style-guide.md` (edit in place beside Max agents). The column `billing_plans.trial_agent_ceiling` is created by C1's migration; this child adds no migration. Tests: API create/update/clear round-trip and `ge=1` refusal; web unit test that the field shows only for per-agent + trial plans and saves blank as null. Dev fixtures: reuse the per-agent trial plan and trialing test tenant C1 created (named in C1's PR body). Credentials & reach: (verbatim rows from the Architecture §7; the dev console host is `app.dev.jaah.ai` — the PRD's "dev.jaah.ai" means this host; operate dev only through the console UI) | Oracle | Credential / identity / environment | Status | |---|---|---| | Dev dispatch, sidebar, Upgrade, plan editor, trial-end move (criteria 1–6, 8–10) | `jaah/debug/main/operator-login` (operator; switches into tenants; Owner role in a test tenant; plain member created by that Owner in the console), dev CP `https://app.dev.jaah.ai` | verified — `credential_get` ok; `GET /` 200, `GET /api/auth/me` 401 (reached, unauthenticated) | | Unit/API/Vitest/Playwright tests | worktree `./dc` stack only | verified — nothing beyond the worktree | | Dev fixtures for criteria 1–3, 6, 8 (a per-agent plan with trial days, a trialing test-tenant subscription on dev's Stripe test account, a node with ≥ 8 free slots for that tenant) | same operator login, console only | deferred — fixture state, not access: the C1 builder creates it through the console (Products → plan; Customers → org + `set_trial_end`) before its dev check; scaled down if fewer slots exist (e.g. ceiling 3 vs N=1) | Out of scope: - Do NOT change trial length logic (`billing_plans.trial_days`, the `trial_days()` eligibility rules) or trial eligibility and reuse rules (`self_serve_rules.trial_used`). - Do NOT lift `IN_TRIAL` for plan, interval or hosting changes. Only the agent-count change is opened. - Do NOT auto-bill the peak, and do NOT change the agent count without an admin's explicit confirmation. - Do NOT stop, pause or evict running tasks or sessions at trial end or on a cap change. - Do NOT change caps or behaviour for flat (non-per-agent) plans, non-trialing subscriptions, or the other caps except as AS2 requires. - Do NOT add a post-trial usage meter or a usage analytics page. - Do NOT restructure the sidebar footer line (badge, name, version, Help). The meter is an addition above it. - Do NOT add nag modals, toasts or emails for upgrade prompting. - Deferred, do NOT build: post-trial used-vs-bought meter; operator Customers view of trial peak; `customer.subscription.trial_will_end` notices. - Do NOT add a migration (C1 owns the column) and do NOT change `caps_of` (C1 owns the cap rule). - Do NOT touch trial-days fields or their rules. Done means: - **9. Trial ceiling setting (D2, AS6).** Operators can set "Trial agent ceiling" per plan in the plan editor. It accepts a blank value (default 10) or a positive integer, persists, and feeds criterion 1. → Verify on dev: edit a per-agent trial plan, set 5, save, reload, observe 5. Criterion 1's dispatch scenario honours it (with N=3 and ceiling 5, 5 run and the rest are held). → Quality: the field is hidden for flat plans and for per-agent plans with no trial days; its placeholder shows the server default; 0 or a negative value is refused with a readable message. - **10. On by default, real deployment.** No feature flag gates criterion 9. → Verify on `app.dev.jaah.ai` with no manual toggle.

Outcome

Trial agent ceiling in the plan editor (API field + server default; shown only for per-agent trial plans; blank = default; 0 refused). Quick gate and changed-area tests green; #6616 merged (a4678b7), deploy ok; dev check: set 5, reload showed 5, restored. Visual check done: clean on desktop and phone. Memory: none applied.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)