The Aegis Constraint Agent is a small educational rebuild of a constraint-gated operating pattern. It does not control equipment. It does not connect to real infrastructure. It does not make compliance, security, aviation, or operational recommendations. It shows one architectural idea clearly: the model can explain the decision, but deterministic Python code owns the gate.
What This Agent Does
The agent runs four fictional mission-review scenarios. Each scenario contains simple operating conditions: weather, battery, airspace, permission, data freshness, and ambiguity.
The Python code turns those conditions into one of three statuses: GO, BLOCKED, or HUMAN_REVIEW. After that status is set, the model receives the result and explains it in plain English.
The important part is the trust boundary. The model does not get to override the decision. It can only explain the decision already made by the gate.
The Control Desk
The easiest way to think about this build is a control desk.
A request arrives. The desk checks the operating conditions. If all required signals pass, the desk marks the case GO. If a hard condition fails, it marks the case BLOCKED. If the context is ambiguous, it routes the case to HUMAN_REVIEW.
The model is not the control desk. The model is the person writing the summary after the desk has already made the call.
That separation matters. In real agent workflows, the expensive failure is not only that a model says something wrong. The expensive failure is when the system treats model text as permission to act.
Vocabulary You Need
Constraint gate: A rule layer that checks whether an action is allowed before anything moves forward.
Deterministic logic: Code that produces the same result from the same input every time.
Fallback explanation: A safe explanation written by the program when the model output is missing, weak, or not aligned with the gate.
Human review: A route for ambiguous cases where the system does not force a yes or no answer.
Model-agnostic: Designed so the model can be changed through configuration rather than code changes.
What This Is Not
This is not a production safety system.
This is not a robotics controller.
This is not an aviation, legal, compliance, security, or operational approval system.
This is not a replacement for qualified review in real environments.
This build is a small educational pattern. It shows how to keep final authority in deterministic code while still using an AI model for explanation.
Why I Built This
I built this because many agent examples blur an important boundary: explanation is not authorization.
A model can produce a convincing paragraph, but that paragraph is not the same thing as a control decision. If the system is going to approve, block, or escalate an action, that authority needs to sit somewhere inspectable.
The AI can help communicate the decision, but the decision itself lives in code I can inspect, test, and reason about.
What Happened
What surprised me: Gemini repeatedly returned short or incomplete explanations. DeepSeek produced better text, but sometimes added unsupported safety language.
What I would change next: I would move the constraints into a small local policy file. That would make the gate easier to review, version, and explain.
Why this matters: The final architecture did not depend on trusting the model output. The gate stayed deterministic, and weak explanations fell back to audited local text.
The Architecture: How It Works
The agent has four parts.
First, it loads LiteLLM configuration from environment variables.
Second, it runs fictional scenarios through deterministic constraint logic.
Third, it asks the configured model to explain the result.
Fourth, it checks the model explanation before printing it. If the explanation is weak, incomplete, or inconsistent with the gate, the program uses a deterministic fallback.
That final output gate became important during testing. Gemini produced truncated explanations more than once. The code caught those cases and used the fallback.
What to Watch When You Run It
Watch the provider sections first. With a comma-separated MODEL_NAME, the script runs each provider separately.
Then watch the status line. That is the deterministic result.
Then watch the explanation. If the model explanation passes the output gate, the script prints it. If not, the fallback explanation appears instead.
In my final test, both providers completed the loop. Gemini mostly fell back to deterministic text. DeepSeek produced one accepted GO explanation and fallback text for the blocked and human-review cases.
The Build: Step by Step
The first important piece is the model configuration. The agent reads the LiteLLM base URL, model name, and API key from the environment. It also supports comma-separated model names for multi-provider testing.
def load_config():
"""Load required environment variables and stop cleanly if any are missing."""
missing = []
for name in REQUIRED_ENV_VARS:
value = os.environ.get(name, "")
if not value or any(marker in value for marker in PLACEHOLDER_MARKERS):
missing.append(name)
if missing:
print("Missing or unconfigured environment variables:")
for name in missing:
print(" - " + name)
print("Copy .env.template to .env and fill in all values.")
raise SystemExit(1)
model_names = parse_model_names(os.environ["MODEL_NAME"])
print("Active models: " + ", ".join(model_names))
return os.environ["LITELLM_BASE_URL"], model_names, os.environ["LITELLM_API_KEY"]
What This Code Is Actually Doing
This code checks whether the agent has the three configuration values it needs before it tries to call a model. It does not print the API key. If the model list contains two names separated by commas, it splits them so the same scenario loop can run against both providers.
The second important piece is the deterministic gate. This is where the status is decided.
def build_constraint_envelope(scenario):
"""Apply deterministic constraint rules and return a mission envelope."""
constraints = {"weather": scenario["weather_ok"], "battery": scenario["battery_ok"], "airspace": scenario["airspace_ok"], "permission": scenario["permission_ok"], "data_freshness": scenario["data_fresh"]}
failed = [FAILED_LABELS[key] for key, value in constraints.items() if not value]
if scenario["ambiguous"]:
status = "HUMAN_REVIEW"
failed = ["Ambiguous operating context requires human review."]
elif failed:
status = "BLOCKED"
else:
status = "GO"
return {"organization": scenario["organization"], "mission": scenario["mission"], "constraints": constraints, "status": status, "blocking_reasons": failed}
What This Code Is Actually Doing
This function acts like the control desk. It checks each condition and creates a final envelope, which is just a structured record of the decision. If anything required fails, the mission is blocked. If the case is ambiguous, it goes to human review. The model is not involved in this decision.
The third important piece is the prompt sent to the model. Notice that the prompt tells the model the decision has already been made.
def build_model_prompt(envelope):
"""Build a prompt that asks the model to explain the fixed gate result."""
reasons = ""
if envelope["blocking_reasons"]:
reasons = " Blocking reasons: " + "; ".join(envelope["blocking_reasons"]) + "."
return (
"A deterministic safety gate evaluated a simulated mission for "
+ envelope["organization"]
+ ".\nMission: "
+ envelope["mission"]
+ "\nFinal status: "
+ envelope["status"]
+ "."
+ reasons
+ "\nThe deterministic safety gate already made the final decision. "
+ "Do not override the status. Explain the result in plain English in 5 sentences or fewer."
)
What This Code Is Actually Doing
This code prepares the model’s assignment. The model receives the organization, mission, final status, and any blocking reasons. The model is asked to explain, not decide. That is the core separation in the build.
The fourth important piece is the output gate. This became necessary because provider output varied during testing.
def explanation_passes_gate(envelope, explanation):
"""Check whether the model explanation matches the deterministic status."""
text = explanation.lower().strip()
if len(text.split()) < 10 or text.endswith(("the", "a", "an", "to", "of", "and", "because")) or any(term in text for term in ["robot", "drone", "aircraft", "autonomous go-ahead", "no risks", "no safety concerns", "safe travel", "automated action", "safety system", "mission.it", "canevaluate"]):
return False
return envelope["status"].lower().replace("_", " ") in text and all(reason.lower().rstrip(".") in text for reason in envelope["blocking_reasons"])
What This Code Is Actually Doing
This code checks the explanation before trusting it enough to print. If the model output is too short, appears cut off, uses unwanted physical-system language, makes absolute safety claims, or fails to mention the deterministic status and blocking reasons, the program rejects it. Rejection does not crash the run. It simply uses the fallback explanation.
The final piece is the multi-provider loop.
def main():
"""Load configuration and run all fictional scenarios."""
base_url, model_names, api_key = load_config()
client = create_client(base_url, api_key)
for model_name in model_names:
print("-" * 60)
print("Provider run: " + model_name)
print("-" * 60)
for scenario in SCENARIOS:
run_scenario(client, model_name, scenario)
What This Code Is Actually Doing
This code runs the same four fictional scenarios against every configured model. In my test, that meant one run for Gemini and one run for DeepSeek. The important detail is that the same deterministic gate is used for every provider, so provider differences only affect the explanation layer.
Errors I Hit During This Build
Error 1: Gemini returned incomplete explanations.
Gemini produced text that ended mid-sentence. I added an output contract gate so short or cut-off explanations fall back to deterministic local text.
Error 2: DeepSeek sometimes added unsupported safety details.
DeepSeek produced useful explanations, but one output used broad safety language that was stronger than the educational demo supported. I tightened the output gate to reject unsupported language and fall back to the deterministic explanation.
Why This Matters
This build closes the main rebuild series with a practical control pattern.
The pattern is : AI explains, code decides.
That is the difference between an agent that sounds governed and an agent that has a reviewable decision boundary. The value is not the paragraph the model writes. The value is the operating envelope that keeps the model from becoming the authority by accident.
For cloud security, AI governance, and enterprise automation, this is one of the patterns that matters most. Every agent that touches real workflows eventually needs a gate, a fallback, and a human-review route.
Tools Used
LiteLLM (MIT): https://github.com/BerriAI/litellm
NosisTech Agent Engineering (MIT): https://github.com/nosistech/agent-engineering
(c) 2026 NosisTech LLC. Licensed under CC BY 4.0.
Use freely, just credit us.