Skip to main content

A human-approval gate for the dangerous things an AI agent does.

Project description

airlock-hitl

A human-approval gate for the dangerous things an AI agent does. This is the Python package; it mirrors the TypeScript package one-to-one. The full story, design notes, and a live dashboard demo are in the repository.

An agent that can act on untrusted input is dangerous: a prompt injection or a plain mistake can make it pay, email, or delete the wrong thing — and a system prompt won't stop it. Airlock assumes the model will be hijacked and puts the safety boundary in the architecture: every tool you mark SENSITIVE pauses for a human to approve, edit, or reject before it runs.

Install

pip install airlock-hitl      # or: uv add airlock-hitl

The distribution is named airlock-hitl; the import is airlock. Requires Python 3.13+.

Quickstart

The agent wants to send an email — the run pauses before it happens, and the email is sent only once you approve.

import asyncio

from airlock import (
    Agent,
    AgentDependencies,
    ApproveDecision,
    CompletionResult,
    FakeLlmProvider,
    InMemoryAuditSink,
    InMemoryEventBus,
    InMemoryRunStore,
    RiskBasedGatePolicy,
    RiskTier,
    SystemClock,
    Tool,
    ToolCall,
    ToolCallId,
    UuidIdGenerator,
)


async def send_email(args):
    print("SENDING:", dict(args))
    return "sent"


# Mark the tool SENSITIVE: it must be approved before it runs.
tool = Tool(
    name="send_email",
    description="Send an email",
    parameters={"type": "object"},
    risk=RiskTier.SENSITIVE,
    handler=send_email,
)

# A scripted model — swap for AnthropicProvider / OpenAiProvider in production.
provider = FakeLlmProvider(
    [
        CompletionResult(
            text=None,
            tool_calls=(ToolCall(id=ToolCallId("1"), name="send_email", args={"to": "alice@example.com"}),),
        ),
        CompletionResult(text="Done.", tool_calls=()),
    ]
)

agent = Agent(
    AgentDependencies(
        provider=provider,
        tools=[tool],
        events=InMemoryEventBus(),
        store=InMemoryRunStore(),
        audit=InMemoryAuditSink(),
        clock=SystemClock(),
        ids=UuidIdGenerator(),
        gate_policy=RiskBasedGatePolicy(),
        system_prompt="You are a support agent.",
    )
)


async def main():
    paused = await agent.run("Email Alice a refund confirmation")
    print(paused.status)  # "awaiting_approval" — nothing sent yet
    done = await agent.resume(
        paused.run_id, paused.approval.request_id, ApproveDecision(approver="you@example.com")
    )
    print(done.status)  # "completed" — now the email was sent


asyncio.run(main())

In production, swap FakeLlmProvider for AnthropicProvider or OpenAiProvider, and the in-memory adapters for RedisRunStore + RedisEventBus so runs pause and resume across processes. Runnable end-to-end demos (CLI approver, Redis, the dashboard) are in the repository.

What you get

  • An Agent that runs a tool-use loop and pauses before any tool you mark RiskTier.SENSITIVE, until a human approves, edits, or rejects it.
  • Model-agnostic providers (AnthropicProvider, OpenAiProvider) over an injected httpx client — no vendor SDKs.
  • A Redis store and Pub/Sub bus for pause/resume across processes, plus in-memory fakes for tests and demos.

Layout (hexagonal — imports point inward)

  • airlock.domain — tools, risk tiers, messages, runs, events, errors. No I/O.
  • airlock.application — the agent loop, the gate policy, and the ports (Protocols).
  • airlock.infrastructure — adapters: providers, the Redis store and bus, audit sinks, clocks, id generators, and the in-memory fakes.
  • airlock.interface — driving adapters: the runner and the approver.
  • airlock.coreSettings (pydantic-settings).

License

MIT — see the repository for contributing and the full engineering write-up.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

airlock_hitl-0.1.1.tar.gz (58.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

airlock_hitl-0.1.1-py3-none-any.whl (29.7 kB view details)

Uploaded Python 3

File details

Details for the file airlock_hitl-0.1.1.tar.gz.

File metadata

  • Download URL: airlock_hitl-0.1.1.tar.gz
  • Upload date:
  • Size: 58.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airlock_hitl-0.1.1.tar.gz
Algorithm Hash digest
SHA256 463bc80cd2057689110bff7072297ddd9d8e1f2f804e519373836fdbe92269eb
MD5 b1f129929680753be45255393b7de2cc
BLAKE2b-256 6282aac7d1af6ad539f3ac7513b70b3dc4d9ec8c39b07ab8044bc06b12764538

See more details on using hashes here.

Provenance

The following attestation bundles were made for airlock_hitl-0.1.1.tar.gz:

Publisher: release.yml on fedorello/airlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file airlock_hitl-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: airlock_hitl-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 29.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for airlock_hitl-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9ce234b8ed7ea817df8e6040fa4704840131e39df6ce1acb6a76e71aba20d42b
MD5 04fd855b769a11183bd8bf97db56951f
BLAKE2b-256 407ba83ea3315922ea1d3c2deb15cc4b59a6ae9643ae6175749ce981db24bbcb

See more details on using hashes here.

Provenance

The following attestation bundles were made for airlock_hitl-0.1.1-py3-none-any.whl:

Publisher: release.yml on fedorello/airlock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page