← LaunchDarklyCONTENT HISTORY

Update to LaunchDarkly

Snapshot Sep 30, 2026 · 23:09 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full 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