← LaunchDarklyCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to LaunchDarkly
Snapshot Sep 30, 2026 · 23:09 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Configure config targeting rules to control which variations serve to different users. Enable percentage rollouts, attribute-based rules, segment targeting, and guarded rollouts.",
"included_files": [
{
"relative_path": "README.md",
"size_in_bytes": 1311
}
],
"name": "configs-targeting",
"skill_md_contents": "---\nname: configs-targeting\ndescription: Configure config targeting rules to control which variations serve to different users. Enable percentage rollouts, attribute-based rules, segment targeting, and guarded rollouts.\ncompatibility: Requires LaunchDarkly API access token with ai-configs:write permission.\nmetadata:\n author: launchdarkly\n version: \"0.1.0\"\n---\n\n# Config Targeting\n\nConfigure targeting rules for configs to control which variations serve to different contexts. Works the same for both completion and agent mode.\n\n## Prerequisites\n\n- LaunchDarkly account with AgentControl enabled\n- API access token with write permissions\n- Project key and environment key\n- Existing config with variations (use `configs-create` skill)\n\n## API Key Detection\n\n1. **Check environment variables** - `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, `LD_API_KEY`\n2. **Check MCP config** - Claude: `~/.claude/config.json` -> `mcpServers.launchdarkly.env.LAUNCHDARKLY_API_KEY`\n3. **Prompt user** - Only if detection fails\n\n## Core Concepts\n\n### Evaluation Order\n\nTargeting rules evaluate in this order (same as feature flags):\n\n1. **Individual targets** - Specific context keys (highest priority)\n2. **Segment rules** - Pre-defined segments\n3. **Custom rules** - Attribute-based conditions (evaluated in order)\n4. **Default rule** - Fallthrough for all others\n5. **Off variation** - When targeting is disabled\n\n### Semantic Patch API\n\nconfig targeting uses semantic patch instructions:\n\n```\nPATCH /api/v2/projects/{projectKey}/ai-configs/{configKey}/targeting\nContent-Type: application/json; domain-model=launchdarkly.semanticpatch\n```\n\n### Key Concepts\n\n- **variationId**: UUIDs, not keys. Always fetch targeting first to get IDs.\n- **Weights**: Thousandths (50000 = 50%, 100000 = 100%)\n- **Clause logic**: Multiple clauses = AND, multiple values = OR\n- **Null attributes**: Rules with null/missing attributes are skipped\n\n## Workflow\n\n### Step 1: Get Targeting (with Variation IDs)\n\n```bash\ncurl -X GET \"https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/targeting\" \\\n -H \"Authorization: {api_token}\" \\\n -H \"LD-API-Version: beta\"\n```\n\nResponse includes `variations` array with `_id` (UUID) for each variation.\n\n### Step 2: Edit the Default Rule\n\nEdit the default rule to serve the variation you created.\n\n> **Important:** The `turnTargetingOn` instruction does not work for configs. Use `updateFallthroughVariationOrRollout` instead.\n\n```bash\n# First, get variation IDs from Step 1 response\n# Then set fallthrough to the enabled variation (e.g., \"Default\" variation)\ncurl -X PATCH \"https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/targeting\" \\\n -H \"Authorization: {api_token}\" \\\n -H \"Content-Type: application/json; domain-model=launchdarkly.semanticpatch\" \\\n -H \"LD-API-Version: beta\" \\\n -d '{\n \"environmentKey\": \"production\",\n \"instructions\": [{\n \"kind\": \"updateFallthroughVariationOrRollout\",\n \"variationId\": \"your-enabled-variation-uuid\"\n }]\n }'\n```\n\n### Step 3: Add Targeting Rules\n\n**Attribute-based rule:**\n\n```bash\ncurl -X PATCH \"https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/targeting\" \\\n -H \"Authorization: {api_token}\" \\\n -H \"Content-Type: application/json; domain-model=launchdarkly.semanticpatch\" \\\n -H \"LD-API-Version: beta\" \\\n -d '{\n \"environmentKey\": \"production\",\n \"instructions\": [{\n \"kind\": \"addRule\",\n \"clauses\": [{\n \"contextKind\": \"user\",\n \"attribute\": \"selectedModel\",\n \"op\": \"contains\",\n \"values\": [\"sonnet\"],\n \"negate\": false\n }],\n \"variation\": 0\n }]\n }'\n```\n\n**Percentage rollout:**\n\n```bash\ncurl -X PATCH \"...\" \\\n -d '{\n \"environmentKey\": \"production\",\n \"instructions\": [{\n \"kind\": \"addRule\",\n \"clauses\": [{\n \"contextKind\": \"user\",\n \"attribute\": \"tier\",\n \"op\": \"in\",\n \"values\": [\"premium\"],\n \"negate\": false\n }],\n \"percentageRolloutConfig\": {\n \"contextKind\": \"user\",\n \"bucketBy\": \"key\",\n \"variations\": [\n {\"variation\": 0, \"weight\": 60000},\n {\"variation\": 1, \"weight\": 40000}\n ]\n }\n }]\n }'\n```\n\n**Set fallthrough (default rule):**\n\n```bash\ncurl -X PATCH \"...\" \\\n -d '{\n \"environmentKey\": \"production\",\n \"instructions\": [{\n \"kind\": \"updateFallthroughVariationOrRollout\",\n \"variationId\": \"fallback-variation-uuid\"\n }]\n }'\n```\n\n## Python Implementation\n\n```python\nimport requests\nimport os\nfrom typing import Dict, List, Optional\n\nclass AIConfigTargeting:\n \"\"\"Manager for config targeting rules\"\"\"\n\n def __init__(self, api_token: str, project_key: str):\n self.api_token = api_token\n self.project_key = project_key\n self.base_url = \"https://app.launchdarkly.com/api/v2\"\n\n def get_targeting(self, config_key: str) -> Optional[Dict]:\n \"\"\"Get current targeting with variation IDs.\"\"\"\n url = f\"{self.base_url}/projects/{self.project_key}/ai-configs/{config_key}/targeting\"\n\n response = requests.get(url, headers={\n \"Authorization\": self.api_token,\n \"LD-API-Version\": \"beta\"\n })\n\n if response.status_code == 200:\n return response.json()\n print(f\"[ERROR] {response.status_code}: {response.text}\")\n return None\n\n def get_variation_id(self, config_key: str, variation_key: str) -> Optional[str]:\n \"\"\"Look up variation UUID from key or name.\"\"\"\n targeting = self.get_targeting(config_key)\n if targeting:\n for var in targeting.get(\"variations\", []):\n if var.get(\"key\") == variation_key or var.get(\"name\") == variation_key:\n return var.get(\"_id\")\n return None\n\n def update_targeting(self, config_key: str, environment: str,\n instructions: List[Dict], comment: str = \"\") -> Optional[Dict]:\n \"\"\"Send semantic patch instructions.\"\"\"\n url = f\"{self.base_url}/projects/{self.project_key}/ai-configs/{config_key}/targeting\"\n\n payload = {\"environmentKey\": environment, \"instructions\": instructions}\n if comment:\n payload[\"comment\"] = comment\n\n response = requests.patch(url, headers={\n \"Authorization\": self.api_token,\n \"Content-Type\": \"application/json; domain-model=launchdarkly.semanticpatch\",\n \"LD-API-Version\": \"beta\"\n }, json=payload)\n\n if response.status_code == 200:\n return response.json()\n print(f\"[ERROR] {response.status_code}: {response.text}\")\n return None\n\n def enable_config(self, config_key: str, environment: str,\n variation_key: str = \"default\") -> bool:\n \"\"\"\n Enable a config by setting fallthrough to an enabled variation.\n\n Note: turnTargetingOn doesn't work for configs. Instead, set the\n fallthrough from the disabled variation (index 0) to an enabled one.\n \"\"\"\n variation_id = self.get_variation_id(config_key, variation_key)\n if not variation_id:\n print(f\"[ERROR] Variation '{variation_key}' not found\")\n return False\n return self.set_fallthrough(config_key, environment, variation_id)\n\n def add_rule(self, config_key: str, environment: str,\n clauses: List[Dict], variation: int,\n description: str = \"\") -> bool:\n \"\"\"Add targeting rule serving a specific variation index.\"\"\"\n instruction = {\n \"kind\": \"addRule\",\n \"clauses\": clauses,\n \"variation\": variation\n }\n if description:\n instruction[\"description\"] = description\n\n result = self.update_targeting(config_key, environment,\n [instruction], f\"Add rule: {description}\")\n if result:\n print(f\"[OK] Rule added\")\n return True\n return False\n\n def add_rollout_rule(self, config_key: str, environment: str,\n clauses: List[Dict],\n weights: List[Dict],\n bucket_by: str = \"key\") -> bool:\n \"\"\"\n Add percentage rollout rule.\n\n weights: [{\"variation\": 0, \"weight\": 50000}, {\"variation\": 1, \"weight\": 50000}]\n \"\"\"\n result = self.update_targeting(config_key, environment, [{\n \"kind\": \"addRule\",\n \"clauses\": clauses,\n \"percentageRolloutConfig\": {\n \"contextKind\": \"user\",\n \"bucketBy\": bucket_by,\n \"variations\": weights\n }\n }], \"Add percentage rollout\")\n if result:\n print(f\"[OK] Rollout rule added\")\n return True\n return False\n\n def set_fallthrough(self, config_key: str, environment: str,\n variation_id: str) -> bool:\n \"\"\"Set default (fallthrough) variation by UUID.\"\"\"\n result = self.update_targeting(config_key, environment, [{\n \"kind\": \"updateFallthroughVariationOrRollout\",\n \"variationId\": variation_id\n }], \"Set fallthrough\")\n if result:\n print(f\"[OK] Fallthrough set\")\n return True\n return False\n\n def target_individuals(self, config_key: str, environment: str,\n context_keys: List[str], variation: int,\n context_kind: str = \"user\") -> bool:\n \"\"\"Target specific context keys.\"\"\"\n result = self.update_targeting(config_key, environment, [{\n \"kind\": \"addTargets\",\n \"variation\": variation,\n \"contextKind\": context_kind,\n \"values\": context_keys\n }], f\"Target {len(context_keys)} individuals\")\n if result:\n print(f\"[OK] Individual targets added\")\n return True\n return False\n\n def target_segment(self, config_key: str, environment: str,\n segment_keys: List[str], variation: int) -> bool:\n \"\"\"Target a segment.\"\"\"\n result = self.update_targeting(config_key, environment, [{\n \"kind\": \"addRule\",\n \"clauses\": [{\n \"attribute\": \"segmentMatch\",\n \"contextKind\": \"\", # Leave blank for segments\n \"op\": \"segmentMatch\",\n \"values\": segment_keys,\n \"negate\": False\n }],\n \"variation\": variation\n }], f\"Target segments: {segment_keys}\")\n if result:\n print(f\"[OK] Segment targeting added\")\n return True\n return False\n\n def clear_rules(self, config_key: str, environment: str) -> bool:\n \"\"\"Remove all targeting rules.\"\"\"\n result = self.update_targeting(config_key, environment,\n [{\"kind\": \"replaceRules\", \"rules\": []}], \"Clear all rules\")\n if result:\n print(f\"[OK] All rules cleared\")\n return True\n return False\n```\n\n## Instruction Reference\n\n> **Note:** `turnTargetingOn` and `turnTargetingOff` do not work for configs. Configs have targeting enabled by default. To \"enable\" a config, set the fallthrough to an enabled variation using `updateFallthroughVariationOrRollout`.\n\n### Rules\n| Kind | Description |\n|------|-------------|\n| `addRule` | Add rule with clauses and variation/rollout |\n| `removeRule` | Remove by ruleId |\n| `replaceRules` | Replace all rules |\n| `reorderRules` | Change evaluation order |\n| `updateRuleVariationOrRollout` | Update what a rule serves |\n\n### Fallthrough\n| Kind | Description |\n|------|-------------|\n| `updateFallthroughVariationOrRollout` | Set default variation or rollout |\n\n### Individual Targets\n| Kind | Description |\n|------|-------------|\n| `addTargets` | Target specific context keys |\n| `removeTargets` | Remove specific targets |\n| `replaceTargets` | Replace all targets |\n\n## Operators Reference\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `in` | Value in list | `[\"premium\", \"enterprise\"]` |\n| `contains` | String contains | `[\"sonnet\"]` |\n| `startsWith` | String prefix | `[\"user-\"]` |\n| `endsWith` | String suffix | `[\".edu\"]` |\n| `matches` | Regex match | `[\"^user-\\\\d+$\"]` |\n| `greaterThan` / `lessThan` | Numeric comparison | `[100]` |\n| `before` / `after` | Date comparison | `[\"2024-12-31T00:00:00Z\"]` |\n| `semVerEqual` / `semVerGreaterThan` | Version comparison | `[\"2.0.0\"]` |\n| `segmentMatch` | Segment membership | `[\"beta-testers\"]` |\n\n## Clause Structure\n\n```json\n{\n \"contextKind\": \"user\",\n \"attribute\": \"email\",\n \"op\": \"endsWith\",\n \"values\": [\".edu\"],\n \"negate\": false\n}\n```\n\n- Multiple clauses = AND (all must match)\n- Multiple values = OR (any can match)\n- `negate: true` inverts the operator\n\n## Rollout Types\n\n### Manual Percentage Rollout\n```json\n{\n \"percentageRolloutConfig\": {\n \"contextKind\": \"user\",\n \"bucketBy\": \"key\",\n \"variations\": [\n {\"variation\": 0, \"weight\": 50000},\n {\"variation\": 1, \"weight\": 50000}\n ]\n }\n}\n```\n\n### Progressive Rollout\n```json\n{\n \"progressiveRolloutConfig\": {\n \"contextKind\": \"user\",\n \"controlVariation\": 1,\n \"endVariation\": 0,\n \"steps\": [\n {\"rolloutWeight\": 1000, \"duration\": {\"quantity\": 4, \"unit\": \"hour\"}},\n {\"rolloutWeight\": 5000, \"duration\": {\"quantity\": 4, \"unit\": \"hour\"}},\n {\"rolloutWeight\": 10000, \"duration\": {\"quantity\": 4, \"unit\": \"hour\"}}\n ]\n }\n}\n```\n\n### Guarded Rollout\n```json\n{\n \"guardedRolloutConfig\": {\n \"randomizationUnit\": \"user\",\n \"stages\": [\n {\"rolloutWeight\": 1000, \"monitoringWindowMilliseconds\": 17280000},\n {\"rolloutWeight\": 5000, \"monitoringWindowMilliseconds\": 17280000}\n ],\n \"metrics\": [{\n \"metricKey\": \"error-rate\",\n \"onRegression\": {\"rollback\": true},\n \"regressionThreshold\": 0.01\n }]\n }\n}\n```\n\n## Common Patterns\n\n### Model Routing by Attribute\n```python\n# Route based on selectedModel context attribute\ntargeting.add_rule(\n config_key=\"model-selector\",\n environment=\"production\",\n clauses=[{\n \"contextKind\": \"user\",\n \"attribute\": \"selectedModel\",\n \"op\": \"contains\",\n \"values\": [\"sonnet\"],\n \"negate\": False\n }],\n variation=0, # Sonnet variation index\n description=\"Route sonnet requests\"\n)\n```\n\n### Tier-Based Variation\n```python\ntargeting.add_rule(\n config_key=\"chat-assistant\",\n environment=\"production\",\n clauses=[{\n \"contextKind\": \"user\",\n \"attribute\": \"tier\",\n \"op\": \"in\",\n \"values\": [\"premium\", \"enterprise\"],\n \"negate\": False\n }],\n variation=0 # Premium model variation\n)\n```\n\n### Segment Targeting\n```python\ntargeting.target_segment(\n config_key=\"chat-assistant\",\n environment=\"production\",\n segment_keys=[\"beta-testers\"],\n variation=1 # Experimental variation\n)\n```\n\n## Error Handling\n\n| Status | Cause | Solution |\n|--------|-------|----------|\n| 400 | Invalid semantic patch | Check instruction format, ops must be lowercase |\n| 403 | Insufficient permissions | Check API token |\n| 404 | Config not found | Verify projectKey and configKey |\n| 422 | Invalid variation | Use index (0, 1, 2...) or UUID from targeting response |\n\n## Next Steps\n\nAfter configuring targeting:\n1. **Provide config URL:**\n ```\n https://app.launchdarkly.com/projects/{projectKey}/ai-configs/{configKey}\n ```\n2. **Monitor performance** with `built-in-metrics`\n3. **Attach judges** with `online-evals`\n4. **Set up guarded rollouts** for automatic regression detection\n\n## Related Skills\n\n- `configs-create` - Create configs with variations\n- `configs-variations` - Manage variations\n- `online-evals` - Attach judges\n- `segments` - Create segments for targeting\n\n## Other Resources\n\n- [Target with AgentControl](https://docs.launchdarkly.com/home/ai-configs/target.md)\n- [Targeting Rules](https://docs.launchdarkly.com/home/flags/target-rules.md)\n- [JSON Targeting](https://docs.launchdarkly.com/home/flags/json-targeting.md)\n- [Guarded Rollouts](https://docs.launchdarkly.com/home/releases/guarded-rollouts.md)\n"
}SHA-256 of public snapshot: 1c01706c44502ec0dee33e5b52f990fdeeb2a5d6da69087d75830c3429345296