Feature flags
Where Pulse reads feature flags from, how to change them, and what happens when the source is unavailable.
Feature flags turn product features on or off for a whole deployment, or for chosen workspaces, teams, roles or users. This page is for the people who run a Pulse deployment. Workspace settings are covered in Administration.
A flag only shows or hides a feature. It never grants access: a feature that is on still enforces the usual permissions.
Where flags come from
Flags live in pulse-api's own flag store. They are platform-wide, so one definition covers every workspace on the deployment. Every component reads them from pulse-api:
| Component | Reads flags through |
|---|---|
| pulse-api and the scheduler | the flag store directly |
| Pulse Agent services (pulse-agent, pulse-ai) | pulse-api's service evaluation endpoint |
| Web, Mac and iOS apps | pulse-api's client endpoint (POST /api/v1/ofrep/v1/evaluate/flags) |
No client contacts Unleash.
Choosing the source
pulse-api, the scheduler and pulse-agent each pick their source with FEATURE_FLAGS_SOURCE:
| Value | Answers from |
|---|---|
pulse | pulse-api's flag store. Use this. |
unleash | An Unleash server, set with UNLEASH_URL and UNLEASH_API_TOKEN. Without them, every flag answers its default. This is the variable's default, kept for older deployments. |
none | Nothing. Every flag answers its default. |
FEATURE_FLAGS_SOURCE=pulseAn empty or unknown value stops the service from starting. Changing the source takes a restart.
- Hosted Pulse runs on
pulse. Unleash is no longer used. - pulse-ctl installations always run on
pulse. pulse-ctl writes the setting itself and refusesFEATURE_FLAGS_SOURCEand anyUNLEASH_*key inservice_env. - pulse-ai has no setting. It always reads from pulse-api.
Unleash is optional. If you run it today, a platform administrator can copy its flags into the store with POST /api/v1/admin/feature-flags/import/unleash?dry_run=true. Remove dry_run to write. Flags that the store cannot represent, such as partial rollouts or variants, are reported instead of imported.
Under unleash, edits saved in the flag store have no effect. Switch to pulse before you manage flags in backoffice.
Default values
Each time pulse-api starts, it adds every flag it knows that the store doesn't hold yet, at that flag's default. A flag an administrator has changed is never reset, so a new installation needs no seeding.
| Flag | Default | Controls |
|---|---|---|
pulse_agent | on | Pulse Agent in the apps and the agent service |
pulse_loops | on | Loops: scheduled and event-triggered agent work. When it is off, Loop routes answer LOOPS_DISABLED and the apps hide Loops. |
subagents | on | Pulse Agent sub-agents |
project_coordinator | off | The Project Coordinator agent |
pulse_live_effects_off | off | A kill switch: on turns off live visual effects in the web app. |
pulse_judgments | off | TypeSafe judgments in Pulse Agent. They send workspace text to typesafe.ai, so the flag stays off until an administrator turns it on. |
environment_runner_routing | off | Routing coding sessions to a runner from the environment's execution target |
agent_apps | off | Third-party agent apps |
Changing a flag
A platform administrator changes flags in backoffice under Feature flags (/feature-flags), or through /api/v1/admin/feature-flags. Every change requires a signed-in session. An API key or an OAuth application token cannot change flags.
A flag is on, off or targeted. A targeted flag holds rules, and it is on when any rule matches. A rule matches when all of its conditions hold:
- the workspace is one of the listed workspaces
- the user is one of the listed users
- the user belongs to one of the listed teams
- the user's workspace role is one of the listed roles
- the user is a platform admin
A targeted flag is off for everyone that no rule matches, whatever its default. Background work such as scheduled Loop runs has no user, so it matches only workspace conditions. Target pulse_loops by workspace.
Every change is kept in the flag's history. Archiving a flag is final: it then answers its default and can't be edited. To copy flags between deployments, export them with GET /api/v1/admin/feature-flags/export and import the file with POST /api/v1/admin/feature-flags/import.
Every service picks up a change within about a minute. Nothing needs a restart or a config apply.
On a deployment you configure yourself, UNLEASH_OVERRIDES=flag=true,other=false on pulse-api forces a value under every source. pulse-ctl doesn't accept it, so on a pulse-ctl installation change the flag in backoffice instead.
When the source is unavailable
A flag read never fails. When the source can't answer, every component falls back the same way:
| Situation | Answer |
|---|---|
No source (none, or unleash without a server) | The flag's default. |
| Starting up, before the first answer | The last known value if there is one, otherwise the default. pulse_loops answers off here, so no Loop runs before the source has answered. Scheduled runs wait and retry. |
| The source stops answering | The last known values, until it recovers by itself. |
| The source doesn't define the flag | The flag's default. |
Backoffice's Feature flags screen warns when edits have no effect because the store is not the source. GET /api/v1/admin/feature-flags reports the source and whether it is ready or stale in status, and pulse-api exports the same as the pulse_feature_flag_source_ready{source} and pulse_feature_flag_source_stale{source} metrics.
Last updated on