Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions BOOKMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ Stateless gateway design, token forwarding, input validation.

---

## Feature Flags

### [Adding a Feature Behind a Flag](docs/internal/developer/adding-a-feature.md)

Step-by-step guide for gating new features. Covers flag naming, registration, backend/frontend gating, visibility, and testing.

### [Feature Flags Overview](docs/internal/feature-flags/README.md)

Unleash integration, admin UI, workspace overrides, API endpoints.

### [Fail Modes Reference](docs/internal/feature-flags/fail-modes.md)

Which methods fail open vs closed, evaluation precedence, naming conventions.

---

## Development Environment

### [Kind Local Development](docs/internal/developer/local-development/kind.md)
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Branch naming conventions:
- Follow the existing code patterns and style
- Write clear, descriptive commit messages
- Keep commits focused and atomic
- **Gate new features behind feature flags** (see [Adding a Feature Behind a Flag](docs/internal/developer/adding-a-feature.md))
- Test your changes locally

### 3. Commit Your Changes
Expand Down Expand Up @@ -321,9 +322,10 @@ npm test

1. **Run all quality checks** for the components you modified
2. **Run tests** and ensure they pass
3. **Update documentation** if you changed functionality
4. **Rebase on latest main** to avoid merge conflicts
5. **Test locally** with Kind if possible
3. **Gate new features behind a feature flag** — all new user-facing features must be flag-gated ([guide](docs/internal/developer/adding-a-feature.md))
4. **Update documentation** if you changed functionality
5. **Rebase on latest main** to avoid merge conflicts
6. **Test locally** with Kind if possible

### PR Description

Expand Down
5 changes: 5 additions & 0 deletions docs/internal/developer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ Welcome to the Ambient Code Platform developer guide! This section covers everyt
- Security patterns
- Error handling

### Feature Flags
- **[Adding a Feature Behind a Flag](adding-a-feature.md)** - Step-by-step guide for gating new features
- **[Feature Flags Overview](../feature-flags/)** - Unleash integration, fail modes, workspace overrides
- **[ADR-0007: Unleash Feature Flags](../adr/0007-unleash-feature-flags.md)** - Architectural decision and rationale

### Component Development
Each component has detailed development documentation:
- [Frontend README](../../components/frontend/README.md) - Next.js development
Expand Down
186 changes: 186 additions & 0 deletions docs/internal/developer/adding-a-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Adding a Feature Behind a Feature Flag

All new user-facing features must be gated behind a feature flag ([ADR-0007](../adr/0007-unleash-feature-flags.md)). This guide walks through the end-to-end process.

## Quick Checklist

- [ ] Choose a flag name following the naming convention
- [ ] Register the flag in `flags.json` (or `models.json` for model flags)
- [ ] Gate the backend handler
- [ ] Gate the frontend component
- [ ] Decide visibility: platform-only or workspace-configurable
- [ ] Test with Unleash disabled (verify fail-closed behavior)

---

## 1. Choose a Flag Name

Follow the `<component>.<feature>.<aspect>` convention:

| Category | Pattern | Example | Fail Mode |
|----------|---------|---------|-----------|
| General | `<component>.<feature>.<aspect>` | `frontend.file-explorer.enabled` | Fail-closed |
| Runner | `runner.<runnerId>.enabled` | `runner.gemini-cli.enabled` | Fail-closed |
| Model | `model.<modelId>.enabled` | `model.claude-opus-4-6.enabled` | Fail-open |

General and runner flags default to **off** when Unleash is unavailable (fail-closed). Model flags default to **on** (fail-open) so model availability is never blocked by flag infrastructure outages.

## 2. Register the Flag

### General / runner flags

Add an entry to `components/manifests/base/core/flags.json`:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify manifest files and Make targets exist

# Check flags.json
if [[ -f "components/manifests/base/core/flags.json" ]]; then
  echo "✓ components/manifests/base/core/flags.json exists"
else
  echo "✗ components/manifests/base/core/flags.json NOT FOUND"
fi

# Check models.json
if [[ -f "components/manifests/base/core/models.json" ]]; then
  echo "✓ components/manifests/base/core/models.json exists"
else
  echo "✗ components/manifests/base/core/models.json NOT FOUND"
fi

# Check Make targets
if grep -q "^deploy-unleash-kind:" Makefile; then
  echo "✓ Make target 'deploy-unleash-kind' found"
else
  echo "✗ Make target 'deploy-unleash-kind' NOT FOUND"
fi

if grep -q "^unleash-port-forward:" Makefile; then
  echo "✓ Make target 'unleash-port-forward' found"
else
  echo "✗ Make target 'unleash-port-forward' NOT FOUND"
fi

Repository: ambient-code/platform

Length of output: 256


🏁 Script executed:

cat -n docs/internal/developer/adding-a-feature.md | sed -n '25,65p'

Repository: ambient-code/platform

Length of output: 1663


🏁 Script executed:

grep -n "deploy-unleash-kind\|unleash-port-forward" docs/internal/developer/adding-a-feature.md

Repository: ambient-code/platform

Length of output: 195


🏁 Script executed:

cat -n docs/internal/developer/adding-a-feature.md | sed -n '150,165p'

Repository: ambient-code/platform

Length of output: 671


make deploy-unleash-kind target missing.

The guide references make deploy-unleash-kind at line 158 to deploy Unleash to a Kind cluster, but this Make target does not exist. The manifest files referenced (components/manifests/base/core/flags.json and models.json) are correct and present. Add the missing Make target or update the documentation with the correct deployment command.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/internal/developer/adding-a-feature.md` at line 32, The docs reference a
non-existent Make target deploy-unleash-kind; either add a Makefile target named
deploy-unleash-kind that runs the appropriate Kind deployment steps to apply the
manifests under components/manifests/base/core (including flags.json and
models.json), or update the documentation to point to the actual existing Make
target that performs the same Kind deployment. Modify the Makefile to include a
deploy-unleash-kind recipe that executes the cluster creation/apply sequence for
the Unleash manifests, or change the doc line referencing deploy-unleash-kind to
the correct target name and include any required flags/notes to deploy to a Kind
cluster.


```json
{
"flags": [
{
"name": "frontend.my-feature.enabled",
"description": "Enable the my-feature UI for session creation",
"tags": [
{
"type": "scope",
"value": "workspace"
}
]
}
]
}
```

Omit the `tags` array to make the flag platform-only (not visible in the workspace admin UI).

### Model flags

Model flags are auto-generated from `components/manifests/base/core/models.json` at startup. Set `"featureGated": true` on the model entry. No `flags.json` entry is needed.

### What happens at startup

The backend syncs `flags.json` and `models.json` to Unleash on boot (`cmd/sync_flags.go`). New flags are created automatically; flags for removed models are archived. The sync requires `UNLEASH_ADMIN_URL` and `UNLEASH_ADMIN_TOKEN` and skips silently if they are not set.

## 3. Gate the Backend

Use the handler-level wrappers in `handlers/featureflags.go`. Choose the right function based on your use case:

```go
import "net/http"

// Option A: Hide the feature entirely (404 when disabled)
if !handlers.FeatureEnabled("frontend.my-feature.enabled") {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}

// Option B: Branch behavior (legacy vs new)
if handlers.FeatureEnabled("frontend.my-feature.enabled") {
handleNewBehavior(c)
} else {
handleLegacyBehavior(c)
}

// Option C: Per-user rollout (Unleash strategies with user context)
if !handlers.FeatureEnabledForRequest(c, "frontend.my-feature.enabled") {
c.JSON(http.StatusForbidden, gin.H{"error": "feature not enabled for you"})
return
}
```

| Function | Fail Mode | When to Use |
|----------|-----------|-------------|
| `handlers.FeatureEnabled(flag)` | Fail-closed | Same result for all users |
| `handlers.FeatureEnabledForRequest(c, flag)` | Fail-closed | Per-user rollout, A/B tests |
| `featureflags.IsModelEnabled(flag)` | Fail-open | Model availability checks |

### Reference files

- `components/backend/featureflags/featureflags.go` — SDK init, `IsEnabled`, `IsModelEnabled`
- `components/backend/handlers/featureflags.go` — handler wrappers

## 4. Gate the Frontend

### Client-side flag (Unleash SDK)

For flags evaluated purely in the browser via the Unleash React SDK:

```tsx
import { useFlag } from '@/lib/feature-flags';

export function MyComponent() {
const enabled = useFlag('frontend.my-feature.enabled');
if (!enabled) return null;

return <NewFeature />;
}
```

### Workspace-scoped flag (backend evaluation)

For flags that respect workspace ConfigMap overrides:

```tsx
import { useWorkspaceFlag } from '@/services/queries/use-feature-flags-admin';

export function MyComponent({ projectName }: { projectName: string }) {
const { enabled, isLoading } = useWorkspaceFlag(projectName, 'frontend.my-feature.enabled');

if (isLoading) return <Spinner />;
if (!enabled) return null;

return <NewFeature />;
}
```

Use `useWorkspaceFlag` when workspace admins should be able to override the flag independently.

## 5. Choose Visibility

| Visibility | Unleash Tag | Who Controls | Use When |
|------------|-------------|--------------|----------|
| Workspace-configurable | `scope: workspace` | Workspace admins + Platform team | Beta opt-in, experimental UI |
| Platform-only | _(no tag)_ | Platform team only | Infrastructure, security, kill switches |

To make a flag workspace-configurable, include the tag in `flags.json` (shown in step 2) or add it manually in the Unleash UI: open the flag > Tags > add type `scope`, value `workspace`.

## 6. Test Locally

### Without Unleash (most common)

When `UNLEASH_URL` is not set, the SDK is not initialized:

- General flags → `false` (fail-closed). Your feature should be **hidden**.
- Model flags → `true` (fail-open). Models should be **available**.

Run through the UI and verify the gated feature is not visible.

### With Unleash

```bash
make deploy-unleash-kind # Deploy Unleash to Kind cluster
make unleash-port-forward # Access at http://localhost:4242
```

Then toggle the flag in the Unleash UI and verify the feature turns on/off without a redeploy.

### Verify fail-closed behavior

1. Stop or remove the Unleash deployment
2. Restart the backend
3. Confirm your feature is hidden (general flags) or available (model flags)

---

## Evaluation Precedence

When a flag is evaluated for a workspace, three layers are checked in order:

1. **Workspace ConfigMap override** (highest priority) — `feature-flag-overrides` ConfigMap in the workspace namespace
2. **Unleash SDK evaluation** — respects strategies, rollout percentages, A/B tests
3. **Code default** (lowest priority) — general: `false`, model: `true`

See [Fail Modes Reference](../feature-flags/fail-modes.md) for the full matrix.

## Further Reading

- [Feature Flags Overview](../feature-flags/) — Unleash integration, admin UI, API endpoints
- [Fail Modes Reference](../feature-flags/fail-modes.md) — fail-open vs fail-closed details
- [ADR-0007](../adr/0007-unleash-feature-flags.md) — architectural decision and rationale
Loading