---
title: Feature flags
description: Turn a Beeuni surface on for one project, and read what every flag gates.
---

A feature flag turns one Beeuni surface on for one project. Any surface can
ship behind a flag — experimental, beta, or fully stable. "Experimental" is a
stability badge on a flag, not the name of the system.

Flags are per project. Turning a flag on in one project changes nothing in
another project, and nothing for other accounts.

<CardGroup>
  <Card icon="app-window" title="Apps" href="/docs/feature-flags/apps">Deploy static sites, bundles, Dockerfiles, and OCI images to stable URLs.</Card>
</CardGroup>

## Turn a flag on

1. Open **Settings → Feature flags**. The tab lists every flag the platform
   supports.
2. Read the row: the flag name, its stability badge, one sentence of
   description, and its origin — `Default on`, `Default off`, or
   `Overridden for this project`.
3. Use the switch. The change applies to the current project immediately.

You need the project's `project.settings.write` permission. The route answers
`403` for any other caller.

### From the CLI

The same switches are available to scripts and agents through `beeuni projects features`:

```bash
beeuni projects features                # every flag: key, state, origin
beeuni projects features enable reminders
beeuni projects features disable reminders
beeuni projects features reset reminders  # drop the override; follow the platform default
beeuni projects features --json          # the full catalog as JSON
```

Add `--project <id>` to act on a project other than the linked/default one. A
flag the platform marks unavailable stays off whatever the project override
says; the CLI prints that as `n/a` / `unavailable`.

:::info[Flag state is not in your repo]
Per-project flag state lives in the database, on the project row. It is never
read from `beeuni.yaml`. A flag you turn on does not travel with a repository
clone or a fork.
:::

## Stability badges

The badge describes the contract, not the switch. A `stable` flag is still an
opt-in: `apps` is stable and still off by default.

| Badge | What it means |
|---|---|
| Experimental | The surface and its contract can still change. |
| Beta | The surface works and the shape is settling. |
| Stable | The contract holds. The flag stays an opt-in. |

## How a flag is enforced

Every flag declares one enforcement mode.

| Mode | What the server does when the flag is off |
|---|---|
| `routes` | The HTTP surface rejects the request with `403`. |
| `behavioral` | The behavior does not occur — no connector materializes, no env injects, no agent registers. |
| `ui-only` | The server deliberately does not enforce. The flag hides client surface only. |

A `routes` rejection is identical everywhere:

```json
{
  "error": "Apps is not enabled for this project. Contact Beeuni to enable it.",
  "code": "feature_disabled",
  "feature": "apps"
}
```

The `error` string names the flag list, not a specific tab. The list is the
**Experimental** tab of Settings.

Branch on `code`, never on the message text. The SDK exports
`isFeatureDisabledError(error)` and `featureDisabledKey(error)` for exactly
this.

## Every flag

Registry order — the same order **Settings → Feature flags** shows.

Apps are enabled per project by Beeuni. Contact Beeuni to request access. When enabled, Settings shows them as **Managed by Beeuni**.

| Key | Name | Stability | Default |
|---|---|---|---|
| `marketplace` | Marketplace | Beta | On |
| `connectors_api_discover` | Connectors API Discover | Experimental | Off |
| `agentmail_email` | AgentMail Email | Experimental | Off |
| `llm_gateway` | LLM Gateway | Experimental | On  |
| `meta_agent` | Meta Agent | Experimental | Off |
| `apps` | Apps | Stable | Off |
| `monitors` | Monitors | Experimental | Off |
| `reminders` | Reminders | Beta | Off |
| `warm_sessions` | Warm Sessions | Beta | On |
| `secrets_egress` | Network-Enforced Secrets | Experimental | On |
| `pi_harness` | Pi Harness  | Experimental | Off |

### What each flag gates

- **`marketplace`** — browse and install skills from community and vendor
  registries.
- **`connectors_api_discover`** — browse direct API, MCP, GraphQL, CLI, and
  Postman surfaces beside managed OAuth apps. See
  [Connectors](/docs/connect/connectors).
- **`agentmail_email`** — assign AgentMail inbox connections so inbound email
  starts and continues sessions.
- **`llm_gateway`** — route the project through the managed Beeuni LLM
  gateway. See [Models](/docs/project/models).
- **`meta_agent`** — add a platform-owned coordinator agent that spawns and
  manages specialized sessions.
- **`apps`** — deploy static sites, bundles, Dockerfiles, and OCI images to
  stable serverless URLs. See [Apps](/docs/feature-flags/apps).
- **`monitors`** — run 24/7 watchers from your repo that fire trigger events
  into sessions. See [Triggers](/docs/connect/triggers).
- **`reminders`** — scheduled check-ins that re-prompt one session, once or
  on repeat. Off, the reminder routes answer `feature_disabled` and existing
  reminders do not fire. See [Reminders](/docs/connect/reminders).
- **`warm_sessions`** — keep one sandbox booted while a project is open, so a
  new session starts without a cold boot.
- **`secrets_egress`** — offer network enforcement for a secret: the sandbox
  holds a handle, and Beeuni substitutes the real value only on requests to
  approved hosts. See [Secrets](/docs/project/secrets).
- **`pi_harness`** — run this project's sessions on the pi agent harness
  inside the ordinary session sandbox (`BEEUNI_HARNESS=pi`). On: every new or
  restarted session boots pi, whatever the manifest says. Off: the manifest
  decides — `runtime: pi` still boots pi, anything else boots default runtime. pi
  calls models only through the LLM gateway: with `llm_gateway` off, every
  session boots default runtime, whatever this flag or the manifest says. Same
  repo layout, agents and skills either way; a running session keeps its
  harness until it is restarted or resumed. Subagents work on pi: the agent
  delegates with the `task` tool to `general`, `explore` (read-only), or any
  project agent with `mode: subagent` in its `.md`, and the transcript
  shows each subagent's steps in place. pi sessions also load pi.dev packages:
  see [`harnesses:`](/docs/project/manifest#harnesses). pi does not serve every
  session feature default runtime does: see the matrix in
  [Harnesses](/docs/work/harnesses#what-each-harness-supports).

### Graduated flags

A graduated feature is on for every project and has no switch.

- **Agents as principals** is standard behavior with no switch. An agent
  session acts as the agent itself — its Beeuni permissions within its ceiling
  role, whoever runs it. See [Agent permissions](/docs/project/permissions).
- **Review Center** graduated out of the flag system. The Review page, the
  sidebar Review row, and the command-palette row show for every project; the
  `project.review.read` and `project.review.act` permissions still apply.
  `review_center` is no longer a flag key: `PATCH /v1/projects/:id/features`
  answers `400` `Unknown feature flag`, and `beeuni projects features enable
  review_center` prints that error and exits `1`. A stored override from before
  graduation has no effect.
- **Computers** graduated out of the flag system. Every member can connect
  their own computer from the project sidebar; the platform's
  `TUNNEL_ENABLED` setting is the only gate. `agent_tunnel` is no longer a flag
  key: `PATCH /v1/projects/:id/features` answers `400` `Unknown feature flag`.
  See [Computers](/docs/connect/computers).
- **Microsoft Teams** graduated out of the flag system. Every project can
  connect Teams from **Connectors → Channels**; a tenant admin still consents
  once, or the project brings its own bot. `teams` is no longer a flag key:
  `PATCH /v1/projects/:id/features` answers `400` `Unknown feature flag`. A
  stored override from before graduation has no effect. See
  [Microsoft Teams](/docs/connect/teams).

## Side effects of a toggle

Some flags converge platform state after the write commits.

| Flag | Effect after the toggle |
|---|---|
| `agentmail_email` | Beeuni re-runs channel-connector materialization, so the connector appears or disappears with the flag. |
| `llm_gateway` | Beeuni propagates the new provider mode to active sandboxes. |

Effects are convergence work, not part of the toggle's success. The API
response does not wait for them. Each effect is retried once, and the
reconcilers behind it are idempotent and re-run on their periodic sweeps.

## Read and set a flag from code

Read the effective per-project state through the project detail, or through the
React hook:

```tsx
import { useFeatureFlag } from '@beeuni/sdk/react';

function AppsNavItem({ projectId }: { projectId: string }) {
  const apps = useFeatureFlag(projectId, 'apps');
  if (!apps.enabled) return null;
  return <Link href={`/projects/${projectId}/apps`}>Apps</Link>;
}
```

`enabled` is `true` only when the server said exactly `true`. A missing project
id, an in-flight query, and an error all resolve to `false`. Gate fail-closed.

Set the project override with the client:

```ts
const p = beeuni.project(projectId);

await p.updateFeatureFlag('apps', true);   // turn it on for this project
await p.updateFeatureFlag('apps', null);   // clear the override, inherit the default
```

`updateFeatureFlag` calls `PATCH /v1/projects/:id/features`. `feature` is one
of `FEATURE_FLAG_KEYS`, exported from `@beeuni/sdk` and typed as
`FeatureFlagKey`. See SDK reference.

Handle a disabled feature by code, not by message:

```ts
import { featureDisabledKey, isFeatureDisabledError } from '@beeuni/sdk';

try {
  await beeuni.project(projectId).apps.list();
} catch (error) {
  if (isFeatureDisabledError(error)) {
    console.log(`${featureDisabledKey(error)} is off for this project`);
  }
}
```
