Feature-Flagging Agent Behavior Changes
Feature flags let you ship agent code and packs dark, then turn behavior on for cohorts without a scramble deploy.
For agents, flags should select pins and policies (prompt version, tools, model route, autonomy), not sprinkle if flag through every prompt string.
Summary
Define flag keys for pin selection and behavior gates, resolve them once per run into a concrete AgentPin, log the flag variation with the pin, roll out by percent or allowlist, and keep kill/rollback as a flag flip to the last safe variation.
Recipe
- Inventory behavior dimensions worth flagging: prompt pack, tool pack, model route, max turns, tool allowlist, autonomy level.
- Store flags in a central service or config file read by all workers (short TTL).
- Resolve flags →
AgentPin+ runtime policy before the first model call. - Use sticky keys (
user_id,org_id,session_id) for percent rollouts. - Default fail-safe: missing flag service → last known good pin, not "newest experimental."
- Gate irreversible tools with separate flags or privilege rings.
- Emit flag keys/values on traces and analytics.
- Clean up flags after full rollout; do not leave permanent boolean soup.
Working Example
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class AgentPin:
prompt_version: str
tool_schema_version: str
model_id: str
max_turns: int = 8
# Simulated flag payload from LaunchDarkly/Unleash/Homebrew config (verify vendors at build)
DEFAULT_FLAGS: dict[str, Any] = {
"agent.support.prompt": "v1",
"agent.support.tools": "tools_v1",
"agent.support.model": "model-a",
"agent.support.canary_prompt": "v2",
"agent.support.canary_percent": 0,
"agent.support.allow_refund_tool": False,
}
def bucket(key: str, flag_name: str) -> int:
raw = hashlib.sha256(f"{flag_name}:{key}".encode()).hexdigest()
return int(raw[:8], 16) % 100
def resolve_pin(subject_key: str, flags: dict[str, Any]) -> AgentPin:
percent = int(flags.get("agent.support.canary_percent", 0))
use_canary = percent > 0 and bucket(subject_key, "agent.support.canary_percent") < percent
prompt = (
flags["agent.support.canary_prompt"]
if use_canary
else flags["agent.support.prompt"]
)
return AgentPin(
prompt_version=str(prompt),
tool_schema_version=str(flags["agent.support.tools"]),
model_id=str(flags["agent.support.model"]),
max_turns=8,
)
def tool_allowed(name: str, flags: dict[str, Any]) -> bool:
if name == "refund_order":
return bool(flags.get("agent.support.allow_refund_tool", False))
return True
def start_run(user_id: str, flags: dict[str, Any] | None = None) -> dict[str, Any]:
f = flags or DEFAULT_FLAGS
pin = resolve_pin(user_id, f)
return {
"user_id": user_id,
"pin": pin,
"refund_enabled": tool_allowed("refund_order", f),
"flag_snapshot": {
"prompt": pin.prompt_version,
"canary_percent": f.get("agent.support.canary_percent"),
},
}
print(start_run("org_9:user_1"))
print(start_run("org_9:user_1", {**DEFAULT_FLAGS, "agent.support.canary_percent": 100}))Deep Dive
What to flag (and what not to)
| Flag | Good use | Avoid |
|---|---|---|
| Prompt / pack version | Progressive delivery | Editing prompt text inside the flag UI without git |
| Model route | Cost/quality experiments | Per-request random model thrash |
| Tool allowlist bit | Risky tool exposure | One mega-flag for "new agent" with no breakdown |
| Max turns / spend cap | Incident tighten | Hiding permanent architecture behind flags |
| Autonomy level | Draft → supervised → auto | Skipping HITL with a quiet default true |
Flags select versioned artifacts. They should not be the only store of the prompt body.
Resolution order
request context (user, org, plan)
→ flag evaluation
→ pin + policy
→ load pack from store
→ agent loop
→ log pin + flag variationResolve once per run. Re-evaluating mid-loop can switch prompts mid-thought.
Cohort strategies
| Strategy | When |
|---|---|
| Percent of users | Broad quality canary |
| Percent of orgs | B2B isolation |
| Allowlist tenants | Design partners |
| Staff / internal | Dogsfood |
| Plan tier | Premium model routes |
| Random per request | Almost never for agents |
Sticky org-level canaries prevent one company from seeing two policies across seats inconsistently - choose deliberately.
Fail-open vs fail-closed
| Failure | Prefer |
|---|---|
| Flag service timeout | Cached last config or baseline pin |
| Unknown flag key | Safe default (old behavior) |
| Invalid pin version | Reject run or fall back baseline + alert |
| Risky tool flag missing | Deny tool |
Agent safety-related flags should fail closed.
Pairing with CI and canary metrics
- PR merges pack
v2while flag still points atv1. - Eval gate already passed for
v2. - Flag percent → 5%; watch SLOs.
- Percent → 100%; then change default in config; remove dead flag later.
See Canary and Shadow Deployments for Agent Updates.
Local and test environments
- Inject flag fixtures in unit tests.
- Snapshot evaluation for critical paths.
- Never require production flag credentials to run the test suite.
Cleanup and debt
Track flags with owners and expiry.
Long-lived agent_v2_enabled flags become untestable matrix hell. After full rollout, make v2 the code default and delete the branch.
Multi-agent systems
Flag at the orchestrator policy level when possible (which specialist pin to call).
Per-node flag sprawl without a single resolved plan is hard to reason about in incidents.
Gotchas
- Prompt text only in the flag payload. No PR review, no eval on change.
- Non-sticky percent flags on chat agents. Jittery behavior mid-session.
- Different flag values per worker without TTL discipline. Split-brain packs.
- Flag true enables refunds globally in prod by mistake. Separate env projects; default false.
- Logging flags but not pin hashes. You know the label, not the bytes.
- Nesting ten flags for one feature. Collapse to a pin ID.
- Using flags instead of kill switches for runaway loops. Kill switches must be faster and simpler - see HITL kill switch docs.
- Forgetting mobile/async workers. All entrypoints must evaluate the same flags.
Alternatives
| Approach | Strength | Weakness |
|---|---|---|
| Central flags → pin (this) | Controlled rollout | Service dependency |
| Redeploy for every prompt change | Simple mental model | Slow; coarse rollback |
| Per-tenant DB prompt overrides | Enterprise flexibility | Version sprawl |
| Config file baked in image | Auditable with image | Slow toggle |
| Header override for staff | Great debugging | Must be auth-gated hard |
FAQs
Which flag product should we use?
Any mature system with percentage rollouts, auth, audit logs, and server SDKs works. Homegrown JSON in S3 is fine at small scale if you add sticky bucketing and cache safely (verify vendor features at build).
Should product managers edit prompts via flags?
They should edit versioned packs through a review workflow. Flags only choose among shipped versions.
How do flags interact with rollback?
Rollback is often "set percent to 0 / pin to previous." That is faster than rebuilding images when packs are externalized.
Can we flag temperature and other decoding params?
Yes, as part of the pin or policy object. Keep them explicit on traces.
How do we test all flag combinations?
You cannot. Test defaults, each critical variation, and fail-closed paths. Exhaustive matrices do not scale.
Are user-agent headers enough for canary?
No. Use authenticated subject keys. Headers are spoofable and unstable.
What about offline or edge agents?
Bundle a default pin; refresh flags when online; do not block the run forever on flag fetch.
Do we need flags if we already have blue/green?
Blue/green flips coarse binaries. Flags still help for pack-only changes and percent canaries inside one binary.
How should we name flag keys?
Namespace by agent and dimension: agent.<product>.<dimension>. Avoid new_thing_enabled.
Can flags replace privilege rings?
No. Flags can choose a ring, but authorization must still enforce tool permissions in host code.
Related
- What "Continuous Deployment" Means for a Prompt-Driven System - CD context
- CI/CD & Agent Lifecycle Basics - first percent router
- Versioning Prompts and Tool Schemas Alongside Code - what flags select
- Canary and Shadow Deployments for Agent Updates - traffic patterns
- Rollback Strategies When a Deployed Agent Regresses - flip back
- Kill Switches: Stopping a Runaway Agent Mid-Execution - emergency stop
- Privilege Rings: Scoping What an Agent Can Do Without Asking - autonomy gates
- CI/CD & Agent Lifecycle Best Practices - checklist
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.