Python imports are execution. Preflight puts a decision in front of them.
the problem it solves
A Python program that loads plugins has an awkward trust boundary: importing a module runs its top-level code. If the host imports a plugin to ask what it is or what tools it provides, the plugin has already run before the host can say no.
Preflight makes that decision from inert JSON on disk. Code that fails the gate never gets an import.
what it is
A manifest and a gate. The plugin author describes the package, entrypoint, tools, risk
levels and release tier in manifest.json. The host supplies an explicit allowlist and
the risks it will accept.
Nineteen checks. Fourteen run before any import happens. They validate the manifest, keep entrypoints inside the trusted plugin folder, enforce load order and policy, and refuse plugin or tool-name collisions. After import, Preflight verifies the module path, the plugin interface and the running plugin's self-description. A failed registration rolls back cleanly.
This is the engineering pattern I care about in agent and AI systems: make authority explicit, put one observable gate in front of execution, and fail closed when the gate cannot prove what it needs.
what it does not do
Preflight does not read source code, detect malware or decide that a plugin is safe. It enforces what a manifest declares. If an accepted plugin lies in code and then deletes files, opens a socket or spawns a process, the current plugin gate cannot stop it.
It is also not a pip gate, an MCP gateway or a working Windows sandbox. Those are adjacent problems, but claiming them now would erase the boundary the project is meant to make clear.
where it stands
The Python plugin gate is published on PyPI and usable today. The 0.8 artifact protocol is an alpha: it can hash local projects and Python archives, bind an approval to the exact artifact and command, revoke that approval, and write integrity-checked local evidence.
The Windows Blast Chambers work is earlier. Its Rust protocol, Job Object setup and fail-closed client foundation exist, but the native service is deliberately inactive. Secure transport, identity, staging, evidence-key custody, AppContainer isolation and the hostile acceptance suite are still work in progress.
That split is intentional and public: the narrow gate works; the larger trust and containment platform is not finished.
why this is the one to run
Everything else here is a system you would have to take my word for. This one is a
pip install away:
pip install preflight-gate
preflight demo
That runs a deliberately broken set of plugins and prints which ones loaded, which were refused, why they were refused, and whether their code had already run. No private repo, prepared environment or access from me.
Here is that run, from an empty virtual environment on a machine that had never seen the package. Two absolute paths are shortened and their line wrapped; nothing else is changed:
[greeter] top-level plugin code is executing
[impostor] top-level plugin code is executing
[janitor] top-level plugin code is executing
preflight | plugins\ | 5 packages found
LOADED greeter Greeter 1.0.0 - 1 tool
REFUSED trespasser never imported
entrypoint module 'json' resolves to '...\Python311\Lib\json\__init__.py',
which is outside the trusted plugin root '...\preflight\_examples\plugins'
REFUSED collider never imported
tool name collision: 'greeter.hello' is already owned by 'greeter'
REFUSED impostor imported, then rejected
runtime manifest for 'example.impostor' does not match its validated package manifest
tools -- undeclared in the manifest: impostor.purge_all_records
LOADED janitor Janitor 1.0.0 - 1 tool
2 loaded, 3 refused -- 2 of the 3 stopped before any of their code ran
The counts at the bottom are checkable rather than asserted. Every example plugin prints
one line as the first statement in its package, and three of those lines appear above.
Only impostor is refused after one — its manifest and its running code disagreed, which
is not a thing you can know before importing it.
The demo closes by pointing at what it cannot do. Run it again with
--refuse destructive and a fourth plugin is turned away for a tool it declared
honestly, while impostor walks through the same flag, because it declared nothing.
what stands behind the release
Every merge runs the suite on three operating systems across four Python versions, plus lint, type checking, a dependency audit, a CodeQL scan on both languages, and a Rust workspace with warnings denied. One job takes all seven of those as dependencies and refuses the merge unless every one reports success. A job that was skipped or cancelled counts as a refusal too, so a check that never ran cannot pass for a check that passed.
Releases go out on a separate path. It builds twice under a fixed timestamp and compares the results byte for byte, publishes to TestPyPI, downloads that exact wheel back, compares it to the one it just built, installs it into a clean environment and runs it. Only then does it publish to PyPI, over trusted publishing, so there is no long-lived token to leak.
Windows is in the test matrix on purpose. The settings code reads the user config
directory from %APPDATA% and compares paths with os.path.normcase. Neither branch
runs anywhere else, and both decide whether a planted file counts as inside a folder
being inspected.
Nobody else has committed here, so that gate has never refused someone else's merge. It has refused plenty of mine.
how I am building it
Preflight is AI-assisted. I use coding agents to implement, test and review, but I set the boundary, write the acceptance criteria and decide what the project may claim. That division is part of the work, not a disclaimer after it.
The project is how I am learning AI engineering from the systems side: harness design, tool admission, approval lifecycles, deterministic evidence, deployment gates and the uncomfortable work of proving that a security boundary is real. Leaving the unfinished parts visible is evidence of that judgment too.