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.
Search across all documentation pages
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.
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.
AgentPin + runtime policy before the first model call.user_id, org_id, session_id) for percent rollouts.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}))| 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.
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.
| 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.
| 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.
v2 while flag still points at v1.v2.See Canary and Shadow Deployments for Agent Updates.
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.
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.
| 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 |
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).
They should edit versioned packs through a review workflow. Flags only choose among shipped versions.
Rollback is often "set percent to 0 / pin to previous." That is faster than rebuilding images when packs are externalized.
Yes, as part of the pin or policy object. Keep them explicit on traces.
You cannot. Test defaults, each critical variation, and fail-closed paths. Exhaustive matrices do not scale.
No. Use authenticated subject keys. Headers are spoofable and unstable.
Bundle a default pin; refresh flags when online; do not block the run forever on flag fetch.
Blue/green flips coarse binaries. Flags still help for pack-only changes and percent canaries inside one binary.
Namespace by agent and dimension: agent.<product>.<dimension>. Avoid new_thing_enabled.
No. Flags can choose a ring, but authorization must still enforce tool permissions in host code.
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.
Reviewed by Chris St. John·Last updated Jul 16, 2026