Skip to main content

Agentic AI execution governance layer

Project description

Limenex

Deterministic stateful governance for AI agents and agentic systems

The safety instructions in your agentic system live in the same context window as a prompt injection attack. One of them will win — and it won't always be yours.

Without an enforcement layer outside the model, a single malicious input can drain an account, exfiltrate credentials, or delete production data.

Limenex is a deterministic enforcement layer between your agent and the real world. A lightweight policy engine that intercepts every consequential action before execution — blocking, escalating, or allowing it based on rules the model cannot override.

Stateful by design, Limenex tracks cumulative state across calls — it catches the 50th $10 charge, not just a single $500 one.

Wire it once at startup. Zero changes to your agent logic or executors.

# Wire once at startup — executor registry is invisible to the agent
charge = make_charge(
    policy_engine,
    registry={
        "stripe": charge_via_stripe,
        "square": charge_via_square,
    }
)

# Agent expresses intent in plain data — no executor knowledge required
await charge(agent_id="agent-1", provider="stripe", amount=49.99, currency="USD")

How it works

AI agents are like employees at your organisation. They can think and innovate freely — but policies draw the line between what they can execute unilaterally and what requires sign-off. That's exactly how Limenex works: what actions are allowed, what requires human approval, and what is never permitted — defined in config, not in a prompt.

flowchart LR
    A[🤖\nLLM Agent] -->|calls skill| B[⚙️ Limenex\nPolicy Engine]
    B -->|ALLOW| C[✅\nExecutor runs]
    B -->|BLOCK| D[🚫\nHard stop]
    B -->|ESCALATE| E[👤\nHuman approval]
    C --> F[🌐 \nReal World<br/>payments · files · \nAPIs · comms]

Verdicts — ALLOW, BLOCK, or ESCALATE

Every governed action produces exactly one of three verdicts:

  • ALLOW — the action proceeds immediately, no intervention
  • BLOCK — the action is hard-stopped, no override possible
  • ESCALATE — the action is paused and routed to a human approver

The distinction between BLOCK and ESCALATE is intentional. Some limits should never be overridden — a hard no, regardless of context. Others represent a threshold where human judgment is appropriate, not a hard prohibition. You choose the verdict per rule, per skill.

Policies — deterministic or semantic

Policies are defined as an ordered list and evaluated in sequence — the engine short-circuits on the first breach. Each policy produces one of the three verdicts above.

Deterministic policies are hard, rule-based checks — cumulative (block if total spend exceeds $50), per-call (block if a single charge exceeds $500), or set-based (escalate if a write targets a protected directory).

Semantic policies are natural language rules evaluated by a separate LLM you provide — "Escalate if the email tone appears aggressive." Same verdict system. No hidden calls. Your model, your rules.

Skills — only those that matter

A skill is a governed wrapper around a single consequential action — charging a payment, writing a file, sending a message. Not every function call needs one. Only the ones that carry real risk.

Skills are vendor-agnostic: filesystem.write is named after what it does, not which library executes it. You inject the executor; Limenex governs whether it runs.


Quickstart

Install Limenex:

pip install limenex

Create .limenex/policies.yaml:

finance.spend:
  policies:
    - type: deterministic
      dimension: spend_usd
      operator: lt
      value: 50.0
      param: amount_usd
      breach_verdict: ESCALATE

Validate it before wiring anything up:

python -m limenex validate
# ✓ Valid — 1 skill, 1 policy (1 deterministic, 0 semantic)

The validate command loads your YAML through the same code path used at runtime, so any typo or misconfiguration surfaces immediately instead of on first agent call. Pass a path explicitly if yours lives elsewhere: python -m limenex validate config/policies.yaml.

Wire up the skill at application startup:

import asyncio
from limenex.core.engine import PolicyEngine, EscalationRequired
from limenex.core.policy_store import LocalFilePolicyStore
from limenex.core.stores import LocalFileStateStore
from limenex.skills.finance import make_spend

policy_store = LocalFilePolicyStore(".limenex/policies.yaml")
state_store  = LocalFileStateStore(".limenex/state.json")
engine       = PolicyEngine(
    policy_store=policy_store, 
    state_store=state_store,
)

# Your payment executor — injected once at startup
async def my_payment_executor(amount_usd: float):
    print(f"Payment of ${amount_usd:.2f} sent")

spend = make_spend(
    engine, 
    registry={
        "acme-payments": my_payment_executor,
    }
)

# Expose to your LLM agent as a tool — plain data parameters only:
# spend(service: str, amount_usd: float)

async def main():
    try:
        await spend(agent_id="agent-1", service="acme-payments", amount_usd=30.0)   # ✓ runs
        await spend(agent_id="agent-1", service="acme-payments", amount_usd=30.0)   # ✗ escalates
    except EscalationRequired:
        print("Action paused — agent-1 has hit the spend limit.")

asyncio.run(main())

The first call executes and Limenex records $30 against agent-1 in .limenex/state.json. The second call never reaches the executor — the engine evaluates $30 (recorded) + $30 (proposed) = $60, sees it would breach the < $50 limit, and raises EscalationRequired before execution.

Open .limenex/state.json to inspect recorded state at any time:

{
  "spend_usd": {
    "agent-1": 30.0
  }
}

For a full worked example — agent intent, escalation handling, approval loop, and integration with LangGraph — see examples/langgraph_example.ipynb.


Contributing

We welcome contributions — see CONTRIBUTING.md for guidelines.

License

Limenex is licensed under the Apache License 2.0.

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

limenex-0.1.0.tar.gz (53.3 kB view details)

Uploaded Source

Built Distribution

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

limenex-0.1.0-py3-none-any.whl (39.5 kB view details)

Uploaded Python 3

File details

Details for the file limenex-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for limenex-0.1.0.tar.gz
Algorithm Hash digest
SHA256 129afd2ad80484ef10703606afe552dbf1b9710f654da535d63ebfb62b3a7712
MD5 533a7c590e322e6afeac7eee63d56e73
BLAKE2b-256 9db9ba89b230e8df0ef88b7fa39aa931e0d235e371e9e8cfb38895fb4c58ef7d

See more details on using hashes here.

Provenance

The following attestation bundles were made for limenex-0.1.0.tar.gz:

Publisher: release.yml on limenex-hq/limenex

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

File details

Details for the file limenex-0.1.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for limenex-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f82585896e064de8281ad7bbc65ebf1be5b6ea5bbdfa2daa11a1ef6ece7a093
MD5 98acd5e387fe2c64178f8056b5f213a2
BLAKE2b-256 3a4240af39b242fde8558b54bb3b0d01b24caaed3603c0da8dd29489ecd38081

See more details on using hashes here.

Provenance

The following attestation bundles were made for limenex-0.1.0-py3-none-any.whl:

Publisher: release.yml on limenex-hq/limenex

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