Typed Python IaC engine: deterministic Atlas-lang config, graph state, parallel reconciliation
Project description
Atlantide
Typed, deterministic Infrastructure-as-Code for Python.
Write infrastructure in real Python — type-checked by your IDE and mypy, not a
templating language — and get the guarantees a config language exists to provide:
the same config always produces the same plan, and the engine can prove it.
Atlantide rests on three ideas:
- Enforced determinism. Configs are plain Python, but executed by Atlas-lang: a bounded interpreter with no clock, no randomness, no environment, no network. Two runs of the same config produce a byte-identical intermediate representation and a stable content hash. Not a convention — the interpreter cannot do otherwise.
- Graph state with Merkle skip. Resources form a dependency graph. A two-phase
Merkle
input_hashletsapplyskip unchanged nodes with zero provider calls, and reconcile independent nodes in parallel. - Per-field mutability. Every field is declared
mutable(),immutable(), orcomputed(). Changing a mutable field is an in-place UPDATE; changing an immutable one is a REPLACE; computed fields never diff. What a change costs is visible in the type, before you run anything.
Install
pip install atlantide # or: uv add atlantide
Python ≥ 3.11.
atlantide init myproject # scaffold a project that already compiles
cd myproject && atlantide plan
init writes an atlantide.toml, a starter config, and a .gitignore that keeps
the state database and the secrets keyfile out of git. The default template uses the
local provider, so it applies with no cloud credentials at all.
A quick look
from atlantide.core import Stack, output
from atlantide.policy import enforce
from atlantide.providers.aws import S3Bucket, SqsQueue
enforce("require-tags", keys=["env"])
enforce("deny-destroy-in-protected", stacks=["prod"])
for env in ["dev", "prod"]:
with Stack(env, region="eu-north-1", name_prefix="atlantide", tags={"env": env}):
assets = S3Bucket("assets", versioning=(env == "prod"))
jobs = SqsQueue("jobs", fifo=True)
output("assets_arn", assets.arn)
atlantide plan infra.py # preview
atlantide apply infra.py # reconcile, in parallel
atlantide apply infra.py # again: all NOOP, zero provider calls
Reading another resource's output (assets.arn) returns a lazy Ref. That is what
wires the dependency edge — no explicit depends_on, no string addresses — and it
resolves to the real value at apply time.
Infrastructure that already exists
Declare it as above, then bind it to what is already running rather than building a second copy:
atlantide import # what is declared but not tracked
atlantide import prod:aws.S3Bucket:assets # found by name
atlantide import prod:aws.Vpc:main vpc-0abc # located by an id AWS assigned
atlantide plan # no changes
A resource whose live settings differ from what the config declares is not
imported — importing it would mean the next apply quietly changes it. import prints
what differs so the config can be reconciled first, or --allow-drift adopts it and
lets the next plan show the update. Nothing about import creates, changes or
deletes anything; the undo is atlantide state rm, which forgets a row and leaves
the resource alone.
How it works
Every plan and apply runs one pipeline. Everything before the diff is pure and
deterministic; everything after it touches the world.
flowchart TB
subgraph pure["Deterministic — no I/O; same input, same bytes"]
direction LR
cfg["infra.py"] --> lang["Atlas-lang<br/>subset check + fuel-bounded eval"]
lang --> ir["IR + canonical JSON (RFC 8785)<br/>stable content hash"]
ir --> dag["Dependency graph<br/>Refs become edges, cycles rejected"]
dag --> merkle["Two-phase Merkle<br/>input_hash per node"]
end
merkle --> diff{"Diff"}
store[("State backend<br/>sqlite · s3+dynamodb · postgres")] -.->|prior hashes| diff
diff --> plan["Plan<br/>ordered, policy-checked"]
plan -->|plan| changeset["Changeset<br/>NOOP · CREATE · UPDATE · REPLACE · DELETE"]
plan -->|apply| exec["Executor<br/>parallel, under a renewed lease"]
exec -->|unchanged hash| skip["Skipped<br/>no provider call"]
exec <-->|create · read · update · delete| prov["Providers<br/>aws · local · random · yours"]
exec -->|fenced writes| store
- Atlas-lang validates the config against a Python subset — no
while,class, dunder access,eval, or non-allowlisted imports — then evaluates it with a fuel budget and deterministic builtins only. - Lowering turns evaluated resources,
Refs andoutput()s into an IR graph.Refs become dependency edges. - Canonicalization serializes that IR to RFC 8785 JSON and hashes it. Two runs
are byte-identical, which is what makes a
.atlasartifact portable. - Graph + Merkle: cycles are rejected (Tarjan), then each node gets a
two-phase
input_hashin topological order — so a change to one resource propagates to everything downstream of it, and nothing else. - Diff compares desired hashes against prior state, yielding a per-node action. Per-field mutability is what decides UPDATE versus REPLACE.
- Plan orders the actions (creates and updates topologically, deletes in
reverse; a REPLACE is destroy-before-create), then policy bindings run against
the changeset — a mandatory violation blocks
applybefore anything happens. - Apply takes a lease over the reachable graph, reconciles independent nodes in parallel, skips Merkle-unchanged nodes entirely, and persists incrementally. On failure it rolls back completed nodes as a saga.
The pure half is why plan needs no credentials, why validate runs in a
pre-commit hook, and why the same compiled artifact can be promoted from staging
to production without re-executing the config.
Main features
Determinism
- No clock, environment, or network in config — the interpreter has no such builtins.
- Content-hashed IR;
buildemits a portable.atlasartifact with provider versions pinned,verifyre-checks it. - Determinism is over (config, inputs) — only inputs the config actually read.
Plans
- Unchanged nodes cost zero provider calls.
applyre-diffs under the lock and refuses if the executed set differs from the approved one (--allow-plan-driftopts out).refreshreports which fields it actually checked; an unchecked field is never claimed in sync.- A resource the provider cannot find is reported but kept.
State
- Backends: sqlite (default), memory, S3 + DynamoDB, Postgres.
- Per-node leases, renewed for as long as a run lives.
- Fenced writes — the store refuses a write from a lease that is no longer the holder.
- Versioned schema, forward migration, refuse-newer.
state backup/restore. - Ctrl-C rolls back rather than abandoning the run mid-graph.
Secrets
atlantide.secret("name")is a handle; plaintext resolves in memory at apply.- Sensitive outputs sealed at rest; logs and audit records redact by construction.
- Value stores: AES-GCM keyfile, environment, SSM Parameter Store.
Escape hatches
--targetnarrows to a resource and its closure,--replaceforces a recreate — both printed in the plan.- A targeted apply leaves unselected state byte-identical.
state rmforgets a row without touching the provider;state unlockbreaks a dead run's lease.
CI
--json: stdout is exactly one JSON document, success or failure.--detailed-exitcode: 0 no changes, 2 changes pending, 1 error.--audit-logappends every run to JSONL, no-ops included.- Prompts are refused without a terminal; pass
--confirm/-y.
Extensibility
- Providers are ordinary Python packages discovered by entry point — the built-ins included.
- Components (L2 constructs) publishable from a git repo, pinned to a commit and content hash, vendored locally.
Documentation
https://atlantide-org.github.io/
- CLI — every command, targeting, exit codes
- Configuration —
atlantide.toml, inputs, profiles - Remote state — S3 and Postgres backends, concurrency, secrets
- Authoring — output combinators, explicit ordering, renames
- Providers — what ships, and writing your own
- Components — L2 constructs, publishing, consuming
- API reference —
atlantide.coreandatlantide.engine - Architecture — what each package is for
Runnable examples: examples/aws/ and examples/components/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file atlantide-0.2.0.tar.gz.
File metadata
- Download URL: atlantide-0.2.0.tar.gz
- Upload date:
- Size: 620.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8e51382f5f580366e31fc126c83cf9341a09b7547d535134fde2e7875c89f8d
|
|
| MD5 |
97384ab35758c40f60654214834a4431
|
|
| BLAKE2b-256 |
11385538fc53cedf566ae45f1ea0edca0fdf274edb742e2975ec217dab66233e
|
Provenance
The following attestation bundles were made for atlantide-0.2.0.tar.gz:
Publisher:
release.yml on atlantide-org/atlantide
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
atlantide-0.2.0.tar.gz -
Subject digest:
b8e51382f5f580366e31fc126c83cf9341a09b7547d535134fde2e7875c89f8d - Sigstore transparency entry: 2256596548
- Sigstore integration time:
-
Permalink:
atlantide-org/atlantide@4710b8954fc2a7982a0e244e08574c4d0fcf5f4f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/atlantide-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4710b8954fc2a7982a0e244e08574c4d0fcf5f4f -
Trigger Event:
push
-
Statement type:
File details
Details for the file atlantide-0.2.0-py3-none-any.whl.
File metadata
- Download URL: atlantide-0.2.0-py3-none-any.whl
- Upload date:
- Size: 382.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b5a8983f5f04060bc4dc447148f83b4bdf3775af026c12c066a5e5c5a66a75a
|
|
| MD5 |
f709ae3bfac59495912b9415cdec966d
|
|
| BLAKE2b-256 |
a44a94ac9ba6a6af3f267e0b982633ca052a03085b007cd91fe0b23300fb6a2c
|
Provenance
The following attestation bundles were made for atlantide-0.2.0-py3-none-any.whl:
Publisher:
release.yml on atlantide-org/atlantide
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
atlantide-0.2.0-py3-none-any.whl -
Subject digest:
3b5a8983f5f04060bc4dc447148f83b4bdf3775af026c12c066a5e5c5a66a75a - Sigstore transparency entry: 2256596555
- Sigstore integration time:
-
Permalink:
atlantide-org/atlantide@4710b8954fc2a7982a0e244e08574c4d0fcf5f4f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/atlantide-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4710b8954fc2a7982a0e244e08574c4d0fcf5f4f -
Trigger Event:
push
-
Statement type: