Platform Features
Canopy Host (and Cortex, independently) gates its capabilities through a single “Platform Features” system - a declared manifest of feature keys plus a resolver that combines each key’s own on/off state with its dependencies. The dashboard, CLI, SDK, MCP servers, and API middleware all read through the same resolved state - there’s no separate capability check hiding somewhere else.
Checking what’s enabled
GET https://api.canopy.pm/platform-features/resolvedNo authentication required, but the response shape depends on who’s asking:
- Anonymous or non-staff caller:
{"features": {"<key>": true, ...}}- only keys that are both enabled and public are listed. A disabled
key is simply absent (never
false); an internal key is always absent regardless of its state, so its existence isn’t disclosed.
- only keys that are both enabled and public are listed. A disabled
key is simply absent (never
- Canopy staff: the full picture for every key -
{enabled, reason, blockedBy, lifecycle, visibility}- including internal-only features.
Via the SDK:
const resolved = await sdk.platformFeatures.resolved();reason is one of enabled, disabled, dependency_disabled,
unknown_feature, or cycle_detected - a feature that depends on a
disabled feature resolves disabled with blockedBy naming the
dependency, even if its own state is enabled.
Launch states (lifecycle)
Every feature has a lifecycle: planned → experimental → preview
→ ga → (eventually) deprecated. This is separate from whether it’s
currently enabled - a feature can be ga and disabled, or preview
and enabled. Treat lifecycle as a maturity/stability signal and the
resolved enabled value as the actual availability signal.
As of today, host.spaces and host.graph are both preview and
enabled - safe to use, not yet a locked contract (see
Spaces). A separate group of automation/infrastructure
features - things like the sandboxed command-execution surface, the
automated maintenance agent, and the residency-chain hop into Cortex -
are currently disabled in production while they finish maturing.
If an SDK/CLI call against one of these returns a feature_disabled
error, that’s this system working as intended, not a bug - check
/platform-features/resolved (as staff, for the full picture) before
assuming something’s broken.
For CLI/SDK users
A disabled feature’s server-side routes return 403 (public features)
or 404 (internal features, indistinguishable from a route that
doesn’t exist). The CLI resolves this client-side too - a gated command
group fails fast with:
The ‘X’ command is currently unavailable - the “Y” platform feature is disabled on this Canopy environment.
This check is fail-open: if the CLI can’t reach the resolver at all (network blip, cache miss), it lets the command through and relies on the server’s own gate as the real authority - a transient client-side failure never blocks you from a feature that’s actually enabled.
For staff: toggling a feature
Staff-only. Every state change requires a reason (audited).
canopy features listcanopy features set <key> enabled --reason "ready for GA"canopy features set <key> disabled --reason "regression found in prod"Equivalently, via the admin API (isCanopyAccount staff only):
GET /admin/platform-featuresGET /admin/platform-features/:key/historyPATCH /admin/platform-features/:key { "state": "enabled" | "disabled", "reason": "..." }Or through the staff console: administrator.canopy.pm → Features -
one table listing both Host and Cortex features side by side, with a
toggle (disabled if the feature isn’t toggleable, or is currently
blocked by a disabled dependency) and a history drawer per feature. The
Cortex half of that table is always read-only from the Host console
today - Cortex doesn’t have its own staff CRUD route yet, only its own
/platform-features/resolved. One feature is not toggleable at all
through this system regardless of staff permissions: the background job
runner (host.job_runner), which is controlled only by the
JOB_RUNNER_ENABLED environment variable on the worker process.
Not every capability launches under this system with per-team or per-environment granularity - overrides today are global (one on/off state per key, platform-wide), not scoped to a specific team or environment.