{"id":"ai-loop-observe-condition-action-pattern","text":"FTL2 AI-loop rules follow an observe → condition → action pattern: the loop runs `observe` to gather state, calls `condition(state)` to decide whether to act, and calls `action(ftl)` if the condition returns True.","truth_value":"IN","source":"entries/2026/05/11/deployments-stargate-rules-harden_stargate_ssh.md","source_url":"","source_hash":"","justifications":[],"dependents":[],"metadata":{"example":"# From cloudflare-stargate/rules/ensure_stargate_linode.py\nobserve = [\n    {\"name\": \"existing_linodes\", \"module\": \"community.general.linode_v4\",\n     \"params\": {\"label\": \"stargate\", \"state\": \"present\"}}\n]\n\nasync def condition(state: dict) -> bool:\n    linode = state.get(\"existing_linodes\", {})\n    if linode.get(\"failed\"):\n        return True\n    instance = linode.get(\"instance\", {})\n    if not instance or instance.get(\"status\") != \"running\":\n        return True\n    return False\n\nasync def action(ftl) -> None:\n    result = await ftl.community.general.linode_v4(\n        label=\"stargate\", type=\"g6-standard-1\", region=\"us-east\",\n        image=\"private/37121878\", state=\"present\",\n    )\n    ip = result.get(\"instance\", {}).get(\"ipv4\", [None])[0]\n    if ip:\n        await ftl.wait_for(host=ip, port=22, timeout=300)"},"explanation":{"steps":[{"node":"ai-loop-observe-condition-action-pattern","truth_value":"IN","reason":"premise"}]}}