Date: 2026-05-11
Time: 15:22
FTL2 is an AI-first Python automation framework that runs Ansible modules directly from Python code without YAML, Jinja2, or the ansible-playbook runtime. It achieves 3-21x speedups over Ansible through native in-process module execution, asyncio-based concurrency, and a gate protocol that pre-builds modules for remote execution. FTL2 is designed for both one-off automation scripts and as infrastructure for full applications, with AI agents as the primary intended user.
async/await), not YAML playbooks. No Jinja2 templating.file, copy, shell); collection modules use fully qualified names (community.general.linode_v4, ansible.posix.firewalld).ftl.groupname targets remote host groups; ftl.local targets localhost (used for API/cloud modules); ftl["hostname"] targets a specific host.async/await.True..ftl2-state.json enables idempotent provisioning with crash recovery. Check with ftl.state.has("name").shell, command, and raw modules are treated as equivalent by the policy engine. Denying one blocks all three, including FQCN variants.Installation:
pip install ftl2 # standard install
pip install ftl2[vault] # with Vault support
uvx ftl2 --help # run directly without install
Core automation pattern:
async with automation(
inventory="inventory.yml",
fail_fast=True,
) as ftl:
await ftl.groupname.module(param=value)
Full configuration options for automation():
| Parameter | Purpose |
|-----------|---------|
| inventory | Path to YAML inventory file |
| fail_fast | Stop on first failure |
| secret_bindings | Dict mapping module globs to env var credentials |
| state_file | Path to state JSON for idempotency/crash recovery |
| vault_secrets | Dict mapping names to Vault KV v2 paths ("app#key" format) |
| policy | Path to policy YAML file |
| environment | Environment name (used by policy rules) |
| gate_modules | Set to "auto" for automatic gate building |
| record | Path to audit JSON file |
Policy YAML syntax:
rules:
- decision: deny
match:
module: "shell"
environment: "prod"
reason: "Use proper modules in production"
- decision: deny
match:
module: "*"
param.state: "absent"
host: "prod-*"
reason: "No destructive actions on production hosts"
Dynamic provisioning pattern:
ftl.add_host("hostname", ansible_host="1.2.3.4", ansible_user="root")
await ftl.local.wait_for(host="1.2.3.4", port=22, timeout=300)
await ftl["hostname"].dnf(name="nginx", state="present")
Vault secrets access:
pw = ftl.secrets["DB_PASSWORD"] # access pulled Vault secret
Requires VAULTADDR and VAULTTOKEN environment variables.
vault_secrets parameter for KV v2 secret retrieval; optional install with ftl2[vault].asyncio.run() as the entry point.uvx direct execution.shell, command, and raw are policy-equivalent — denying one denies all three.ftl.local is used for API/cloud modules (connection: local equivalent).pip install ftl2[vault] and VAULTADDR/VAULTTOKEN env vars..ftl2-state.json; check existence with ftl.state.has("name").module, environment, host, and param.* fields.PolicyDeniedError is raised when a policy rule blocks an action.