diff --git a/backend/src/apis/shared/feature_flags.py b/backend/src/apis/shared/feature_flags.py index 58554484..b6cffd0c 100644 --- a/backend/src/apis/shared/feature_flags.py +++ b/backend/src/apis/shared/feature_flags.py @@ -67,13 +67,16 @@ def agents_enabled() -> bool: """Whether the Agent Designer surface is enabled for this environment. Gates the ``/agents/*`` alias router (the governed Agent read/write surface over - the evolved assistant store). **Defaults off** (the ``MEMORY_SPACES_ENABLED`` - pattern): set ``AGENTS_API_ENABLED=true`` to turn it on. The feature ships - incrementally across several PRs (contract → surface → resolution → Designer UI), - so it stays dark until complete — the assistant store and its ``/assistants/*`` - surface are unaffected while off. + the evolved assistant store). **Default ON with a kill switch** (house style, + mirroring ``scheduled_runs_enabled``): unset or empty resolves to enabled; only + the literal ``"false"`` (case-insensitive) disables. The CDK side threads + ``config.agents.enabled`` into this env var with the same empty-string-safe + ternary, so an unset GitHub Actions variable can never silently turn it off. The + Agent Designer shipped across several PRs (contract → surface → resolution → + Designer UI → binding reflection); now complete, it defaults on. Gates *feature existence* per environment; *who* may use a specific agent is the - identity-based access check already enforced by the assistant service. + identity-based access check already enforced by the assistant service, and the SPA + nav is separately preview-gated (system-admin) until Assistants are deprecated. """ - return os.environ.get("AGENTS_API_ENABLED", "false").lower() == "true" + return os.environ.get("AGENTS_API_ENABLED", "").strip().lower() != "false" diff --git a/infrastructure/lib/config.ts b/infrastructure/lib/config.ts index 1ad0dd5c..2203190d 100644 --- a/infrastructure/lib/config.ts +++ b/infrastructure/lib/config.ts @@ -172,11 +172,12 @@ export interface MemorySpacesConfig { } /** - * Agent Designer feature flag. Default OFF with an on switch — the governed - * `/agents/*` surface is opt-in per environment, enabled with - * CDK_AGENTS_API_ENABLED=true (or an `agents.enabled: true` cdk.json context). - * Sets the AGENTS_API_ENABLED env var on app-api. The feature ships - * incrementally, so it stays dark until complete; `/assistants/*` is unaffected. + * Agent Designer feature flag. Default ON with a kill switch — the governed + * `/agents/*` surface ships everywhere now that the Designer is complete; only + * CDK_AGENTS_API_ENABLED=false (or an `agents.enabled: false` cdk.json context) + * turns it off. Sets the AGENTS_API_ENABLED env var on app-api + inference-api. + * `/assistants/*` is unaffected, and the SPA nav stays preview-gated until + * Assistants are deprecated. */ export interface AgentsConfig { enabled: boolean; @@ -342,13 +343,15 @@ export function loadConfig(scope: cdk.App): AppConfig { : scope.node.tryGetContext('memorySpaces')?.enabled ?? true, }, agents: { - // Default OFF with an on switch (same pattern as memorySpaces): opt-in per - // environment. The workflow forwards an EMPTY STRING when unset, so treat - // empty/unset as the default (off) and only the literal "true" as on. An - // `agents.enabled` cdk.json context can also force it on. + // Default ON with a kill switch (house style, mirroring memorySpaces / + // scheduledRuns): the workflow forwards an EMPTY STRING when unset, so treat + // empty/unset as the default (on) and only the literal "false" as the off + // switch. An `agents.enabled` cdk.json context can also force it off. The + // Agent Designer is complete, so it ships on everywhere; the SPA nav stays + // preview-gated (system-admin) until Assistants are deprecated. enabled: process.env.CDK_AGENTS_API_ENABLED - ? process.env.CDK_AGENTS_API_ENABLED === 'true' - : scope.node.tryGetContext('agents')?.enabled ?? false, + ? process.env.CDK_AGENTS_API_ENABLED !== 'false' + : scope.node.tryGetContext('agents')?.enabled ?? true, }, fineTuning: { additionalCorsOrigins: process.env.CDK_FINE_TUNING_CORS_ORIGINS || scope.node.tryGetContext('fineTuning')?.additionalCorsOrigins, diff --git a/infrastructure/test/app-api-environment.test.ts b/infrastructure/test/app-api-environment.test.ts index a83bcd11..35fde347 100644 --- a/infrastructure/test/app-api-environment.test.ts +++ b/infrastructure/test/app-api-environment.test.ts @@ -41,7 +41,9 @@ describe('buildAppApiEnvironment — Memory Spaces', () => { expect(on.MEMORY_SPACES_ENABLED).toBe('true'); }); - it('gates the /agents surface on AGENTS_API_ENABLED (default off)', () => { + it('threads config.agents.enabled into the AGENTS_API_ENABLED env var', () => { + // The mock config sets agents.enabled=false explicitly; the loadConfig default + // is ON with a kill switch (covered in config.test.ts). const off = buildAppApiEnvironment(createMockConfig(), stubParams()); expect(off.AGENTS_API_ENABLED).toBe('false'); diff --git a/infrastructure/test/config.test.ts b/infrastructure/test/config.test.ts index 9522dba6..2ac9365b 100644 --- a/infrastructure/test/config.test.ts +++ b/infrastructure/test/config.test.ts @@ -422,6 +422,44 @@ describe('RAG Ingestion Configuration', () => { }); }); + // ============================================================ + // Agents API (Agent Designer) feature flag — default ON with a kill switch + // (complete feature; ships enabled for forkers, empty var must not disable) + // ============================================================ + + describe('Agents API feature flag', () => { + test('defaults to enabled when CDK_AGENTS_API_ENABLED is unset', () => { + delete process.env.CDK_AGENTS_API_ENABLED; + + expect(loadConfig(app).agents.enabled).toBe(true); + }); + + test('treats empty string (unset GitHub Actions variable) as enabled', () => { + process.env.CDK_AGENTS_API_ENABLED = ''; + + expect(loadConfig(app).agents.enabled).toBe(true); + }); + + test('CDK_AGENTS_API_ENABLED="false" is the kill switch', () => { + process.env.CDK_AGENTS_API_ENABLED = 'false'; + + expect(loadConfig(app).agents.enabled).toBe(false); + }); + + test('CDK_AGENTS_API_ENABLED="true" stays enabled', () => { + process.env.CDK_AGENTS_API_ENABLED = 'true'; + + expect(loadConfig(app).agents.enabled).toBe(true); + }); + + test('cdk.json context agents.enabled=false disables when env is unset', () => { + delete process.env.CDK_AGENTS_API_ENABLED; + app.node.setContext('agents', { enabled: false }); + + expect(loadConfig(app).agents.enabled).toBe(false); + }); + }); + // ============================================================ // Configuration Validation Tests // ============================================================