Dynamic access controls
Agenta EE ships with code-default plans, entitlements, and role catalogs. Operators can override any of these at runtime by setting JSON environment variables.
The variables on this page (plans, entitlements, roles) are Enterprise-only.
The simpler sign-up and organization-creation controls
(AGENTA_ACCESS_ALLOWED_DOMAINS, AGENTA_ACCESS_ALLOWED_OWNER_EMAILS, and the
block lists) work in both editions; they are covered in
Restrict Sign-ups and Organization Creation.
This page documents the access layer:
AGENTA_ACCESS_PLANS— plan slugs and per-plan entitlement controls (flags, counters, gauges, throttles).AGENTA_ACCESS_ROLES— custom roles per scope on top of the platform minima (owner/viewer).AGENTA_ACCESS_ROLES_OVERLAY— small deployment-wide role-catalog patches.
These env vars are parsed once at process startup. After changing them, restart all API and worker processes — they each load the controls into memory and will otherwise enforce different limits.
Docker Compose users set these as environment variables in the env file passed
with --env-file. Kubernetes users should set the matching Helm values under
agenta.access in values.yaml; the chart renders them into backend
environment variables for the API, workers, cron, and Alembic job.
| Helm value | Environment variable |
|---|---|
agenta.access.plans | AGENTA_ACCESS_PLANS |
agenta.access.roles | AGENTA_ACCESS_ROLES |
agenta.access.rolesOverlay | AGENTA_ACCESS_ROLES_OVERLAY |
agenta.access.defaultPlan | AGENTA_ACCESS_DEFAULT_PLAN |
agenta.access.defaultPlanOverlay | AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY |
If any override var is set, validation runs at startup:
- invalid JSON → fail
- unknown flag / counter / gauge / permission slug → fail
AGENTA_ACCESS_PLANSempty object → fail- plan entry with no entitlement info and no
description→ fail AGENTA_ACCESS_ROLESredefining the reservedownerorviewerslug → fail- empty scope list → fail
- duplicate role slug within a scope → fail
Run staging deploys with the new values before pushing to production.
AGENTA_ACCESS_PLANS
JSON object keyed by plan slug. The set of keys is the effective plan set — runtime plan references must point to one of these slugs.
Top-level shape
{
"<plan_slug>": <PlanEntry>,
...
}
PlanEntry fields
Every entry must define at least one of description, flags,
counters, gauges, or throttles.
| Field | Type | Required | Description |
|---|---|---|---|
description | string | one-of | Operator-facing description (not shown to users). Description-only entries are accepted for display-only/custom plans. |
flags | object | one-of | Map of flag slug → bool. See flag keys. |
counters | object | one-of | Map of counter slug → Quota. See counter keys. |
gauges | object | one-of | Map of gauge slug → Quota. See gauge keys. |
throttles | array | no | List of Throttle entries. See throttles. |
Quota shape
Used by counters and gauges map values.
| Field | Type | Default | Description |
|---|---|---|---|
free | integer | null | null | Free-tier allowance before paid or capped usage applies. |
limit | integer | null | null | Hard cap; null = unlimited. |
strict | boolean | null | null | If true, the request that would cross the limit is itself rejected (no "one free overshoot"). null is treated as false. |
retention | integer | null | null | Retention window in minutes. Must be one of the canonical Retention enum values: 0 (ephemeral), 60 (hourly), 1440 (daily), 44640 (monthly ≈ 31d), 131040 (quarterly ≈ 91d), 525600 (yearly ≈ 365d). Used by traces_ingested and events_ingested for retention flush. |
scope | string | null | null | Granularity of the meter row: "organization" (default when null), "workspace", "project", or "user". |
period | string | null | null | Metering bucket: "daily", "monthly", "yearly". null means non-periodic (used for gauges). Replaces the pre-reshape monthly: true boolean. |
Flag keys
rbac, audit, access, domains, sso. Values are booleans.
Counter keys
evaluations_run, traces_ingested, traces_retrieved, credits_consumed, events_ingested.
traces_ingested and events_ingested are independent retention domains: each has its own
counter, its own retention window, its own admin flush endpoint
(/admin/spans/flush and /admin/events/flush), and its own cron
schedule. Setting one does not affect the other. events_ingested is
also an enforced usage counter: event publishing performs a soft quota
check before queueing, the events worker applies the authoritative meter
adjust, and operators can set limit as well as retention per plan.
traces_retrieved is the only counter with a non-default scope and
period in the code defaults: scope=user, period=daily, declared on
every plan with limit=null (unlimited). Operators who want to cap
per-user daily reads set limit via env override.
Gauge keys
users.
Throttle shape
| Field | Type | Description |
|---|---|---|
bucket.capacity | integer | Max tokens in the bucket. |
bucket.rate | integer | Tokens added per minute. |
bucket.algorithm | string | null | Optional algorithm tag. |
mode | string | "include" or "exclude". |
categories | array | null | Endpoint categories the throttle applies to. |
endpoints | array | null | Explicit [method, path] pairs. |
Example — self_hosted_enterprise (the code-default for self-hosted)
When AGENTA_ACCESS_DEFAULT_PLAN is unset, signup onboards new
organizations on self_hosted_enterprise. This is the operative plan for
almost every self-hosted deployment, and the canonical shape to use as a
starting point for any further customization via AGENTA_ACCESS_PLANS:
{
"self_hosted_enterprise": {
"description": "Self-hosted enterprise — full access, no quotas.",
"flags": {
"rbac": true,
"audit": true,
"access": true,
"domains": true,
"sso": true
},
"counters": {
"evaluations_run": {"strict": true, "period": "monthly"},
"traces_ingested": {"period": "monthly"},
"traces_retrieved": {"strict": true, "scope": "user", "period": "daily"},
"credits_consumed": {"strict": true, "period": "monthly"},
"events_ingested": {"period": "monthly"}
},
"gauges": {
"users": {"strict": true}
}
}
}
What's notable about the code-default shape:
- All five flags are
true. RBAC, audit-log access, access controls, custom domains, and SSO are all on by default. - No counter has a
limit. Every counter islimit: null(omitted), so the meters layer tracks usage but never blocks a request.strict: trueis structurally present so it kicks in the moment alimitis added. traces_ingestedretention is unset. The tracing-flush job iterates plans and skips this one — traces are kept forever unless you opt in.events_ingestedretention is also unset. Same behavior for events.usersis unlimited. No seat cap;strict: truewould only enforce if you add alimit.
Restating the whole plan via AGENTA_ACCESS_PLANS is necessary only when
you want to define the full effective plan catalog from scratch (or run
several plans side-by-side). For changing one or two fields on the
default plan, AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY
is the right tool — see the worked examples below.
Worked example — per-user, per-day trace-retrieval limit
Counter.TRACES_RETRIEVED ships on self_hosted_enterprise with
scope=user, period=daily, strict=true, and limit=null (unlimited).
The structural plumbing is in place; setting a real number flips the cap
on without any code change.
Use AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY
to cap each user at 1,000 trace retrievals per day. Restate every quota
field explicitly — the overlay is a field-merge, so fields you omit
inherit from the base, but spelling everything out makes the intended
shape obvious in a diff and survives future changes to the base plan:
{"counters": {"traces_retrieved": {"limit": 1000, "strict": true, "period": "daily", "scope": "user"}}}
What it means at runtime:
- The meters DAO persists one row per
(organization_id, workspace_id, project_id, user_id, year, month, day, key)tuple. Different users get different rows; the same user on different days gets different rows. - Every trace/span fetch or query handler runs
check_entitlements(key=Counter.TRACES_RETRIEVED, delta=<distinct traces returned>)in hard-adjust mode. The first request that would push that user's daily counter past 1000 getsHTTP 429 Too Many Requestsand the meter is not bumped — becausestrict=truemakes the DAO predicategreatest(value + delta, 0) <= limit, the request that crosses the line is itself rejected (no "one free overshoot"). - The usage rollup sums every matching row for today across users, so the UI can reflect total org-wide daily retrievals. The per-user cap is enforced; the org-wide display is informational.
- The counter is tracked and surfaced internally for enforcement and usage display.
This is the pattern for any per-scope, per-period counter: the meters
layer handles the per-row bookkeeping automatically based on what the
quota declares for scope and period.
Worked example — seat cap via overlay
To cap users at 50 seats for a single-instance self-hosted deployment,
set the overlay with every gauge field restated explicitly:
{"gauges": {"users": {"limit": 50, "strict": true}}}
The gauge starts blocking the 51st invite immediately after restart.
AGENTA_ACCESS_ROLES
JSON object keyed by scope. Scope values are non-empty arrays of custom
role entries. The owner and viewer minima are platform-managed and
always synthesized for every scope — env can only add roles, never
redefine the minima.
Top-level shape
{
"<scope>": [<RoleEntry>, ...],
...
}
Recognized scopes: organization, workspace, project. Unknown scopes
fail startup. Omitted scopes keep their full code defaults.
RoleEntry fields
| Field | Type | Required | Description |
|---|---|---|---|
role | string | yes | Slug; cannot be owner or viewer (reserved). |
description | string | no | Human-readable description for UIs. |
permissions | string[] | yes | Permission enum slugs, or "*" for full access. |