Back to blog

Human-in-the-loop · Jun 1, 2026

Why Your HITL Rules Should Live in Configuration, Not in Code or Prompts

Putting HITL approval rules in code creates rigid, slow-to-change systems. Putting them in prompts creates unreliable, bypassable suggestions. The right place is declarative, version-controlled configuration — a policy manifest the agent runtime enforces deterministically.

HITLPolicy EngineAgent ArchitectureConfiguration ManagementHuman Oversight

Why Your HITL Rules Should Live in Configuration, Not in Code or Prompts

There are exactly three places to put human-in-the-loop approval rules. Two of them are wrong.

In codeif action.type == 'delete' then require_approval() — creates rigid, slow-to-change logic that requires a developer, a PR, a review, and a deployment to adjust a dollar threshold. Operations teams can't change policy without engineering help. Every policy tweak becomes a software release.

In system prompts — "If the proposed action modifies customer data, ask a human for approval before executing" — creates the illusion of governance. The model might follow it. It might not. When it does follow it, it might interpret "modifies customer data" differently on Tuesday than it did on Monday. Prompt-based HITL is governance theater — it looks like oversight and delivers randomness.

In version-controlled configuration — a declarative, YAML/JSON policy manifest that sits outside both the codebase and the model context — is the only place HITL rules behave like actual governance. The agent runtime reads the manifest and enforces it deterministically. The model never sees the rules. The model cannot argue with the rules. The rules don't change unless someone with the right permissions changes them — and every change is versioned and auditable.

This isn't a philosophical distinction. It's the architectural difference between HITL as a safety net and HITL as a policy.


The Three Locations, Compared

LocationChange SpeedEnforcementAuditabilityRisk
CodeSlow (PR + deploy)✅ Deterministic✅ Versioned in GitOperations can't adjust policy without engineering
PromptInstant❌ Probabilistic❌ EphemeralModel bypass, hallucination, inconsistency
Config manifestFast (no deploy)✅ Deterministic✅ Versioned, auditableRequires policy engine that supports hot-reload

Code gives you reliability but no operational flexibility. Prompts give you flexibility but no reliability. Configuration gives you both — if your runtime supports it.


What Goes In the Policy Manifest

A HITL policy manifest should answer five questions for every action the agent can take:

1. Does this action require human approval?

Boolean. Not a confidence threshold, not a model judgment. A deterministic yes/no based on the action type, classified by the developer when the action is added to the manifest.

2. Under what conditions?

Dollar thresholds, user roles, environments, data sensitivity levels. Conditional rules that modify the default yes/no based on runtime parameters the agent provides.

3. Who approves it?

A specific role, a reviewer pool, a named individual, or an escalation chain. The manifest maps actions to approvers — the policy engine handles routing.

4. How long does the reviewer have?

Timeout windows per action type, with escalation paths for each rung in the chain. The manifest defines the SLA. The runtime enforces it.

5. What happens if nobody responds?

Auto-deny, auto-approve (only for low-risk reversible actions), auto-escalate, or auto-retry. The manifest carries the default — the agent doesn't decide.

A minimal manifest looks like this:

actions:
  deploy_to_production:
    severity: high
    reversible: false
    approval_required: true
    approvers:
      - role: "senior_engineer"
      - role: "engineering_manager"
        escalation_after_minutes: 30
    timeout_minutes: 15
    on_timeout: deny
    on_timeout_escalation: true

  modify_customer_account:
    severity: high
    reversible: false
    approval_required: true
    approvers:
      - role: "support_lead"
    conditions:
      - field: "account.value"
        operator: "greater_than"
        value: 5000
        require_approval: true
      - field: "action.type"
        operator: "equals"
        value: "delete"
        require_approval: true
    timeout_minutes: 30
    on_timeout: deny

  query_read_only_api:
    severity: low
    reversible: true
    approval_required: false
    audit_log: true  # Logged but autonomous

Notice what's missing: no confidence thresholds, no model heuristics, no prose instructions for the LLM. Every rule is a boolean condition evaluated deterministically. The policy engine reads the manifest, checks the conditions, and routes the action. The agent proposes. The engine decides.


Why Manifest-Based Policy Scales — and Prompt-Based Doesn't

The scaling argument for configuration-based policy is straightforward: when you have one agent workflow, prompt-based rules seem fine. When you have 20 — across different teams, different risk profiles, different compliance requirements — prompt-based rules become a coordination nightmare.

Manifests are composable. The finance team defines approval rules for financial actions. The infrastructure team defines rules for deployment actions. The security team defines rules for data access. Each team owns its domain. The runtime merges the manifests into a single policy evaluation layer. No team needs to coordinate with any other team.

Manifests are testable. You can write unit tests against a manifest: "Given this action with these parameters, does the policy engine route it correctly?" You cannot reliably test whether an LLM will follow a system prompt instruction in production.

Manifests are auditable. Every change to the manifest is a Git commit with an author, a timestamp, and a review history. When a regulator asks "who changed the approval threshold on financial transactions from $5,000 to $50,000?" — the answer is in the commit log. When they ask "did the model follow the approval rules on transaction 847291?" — the answer exists in the runtime enforcement log, not in the model's internal reasoning (which is opaque and non-deterministic).

Manifests are hot-reloadable. A well-designed policy engine reads the manifest on every evaluation — or caches it with an invalidation mechanism on change. This means policy changes take effect in seconds, without restarting the agent, without redeploying the runtime. The feedback loop from "we should change this threshold" to "the threshold is changed in production" is measured in seconds, not sprint cycles.


Where Facio Fits

Facio's HITL primitives are built on a manifest-based policy model. Every tool call passes through a deterministic policy evaluation layer that reads from version-controlled configuration. The agent proposes an action. Facio evaluates it against the manifest. If the manifest says "route to human," the agent pauses — regardless of what the system prompt says. If the manifest says "auto-approve," the action executes — with full audit logging.

The policy engine is the enforcement point. It doesn't trust the model to follow rules. It enforces the rules at the execution boundary — after the model has reasoned, before the action touches the world.

Placet.io handles the human side of the pipeline — delivering the approval request to the right reviewer, in their working channel, with the full context the manifest specified. The manifest defines the what (what needs approval, from whom, under what conditions). The runtime enforces the when. Placet.io handles the how.


The Migration Path: From Prompts to Manifests

If your HITL rules currently live in system prompts, migration doesn't require a rewrite. Start with the highest-risk action types:

  1. Identify the highest-severity actions your agents can take (data deletion, external communication, financial transactions, infrastructure changes)
  2. Extract the approval rules from your prompts into a minimal manifest — keep the prose rules in the prompt, add the manifest as a deterministic backstop
  3. Test the manifest against production traffic in shadow mode — log what the manifest would have decided, compare with what actually happened
  4. Switch enforcement to the manifest — remove the prompt instructions once the manifest covers the same logic deterministically
  5. Iterate — add conditional thresholds, approver routing, escalation chains, and timeout defaults

You don't need to move everything at once. A single high-risk action governed by a manifest is better than ten actions governed by prompts. One deterministic gate that actually works is worth more than ten probabilistic suggestions.


Key Takeaways

  • HITL in code is rigid but reliable. HITL in prompts is flexible but unreliable. HITL in configuration is both — if your runtime supports it
  • A policy manifest answers five questions per action: approval required, conditions, approver, timeout, and default-on-timeout
  • Manifests are composable, testable, auditable, and hot-reloadable — four properties prompts can't provide and code can't provide at operational speed
  • The enforcement point is outside the model: the agent proposes an action, the policy engine reads the manifest and routes, the dispatcher waits for clearance or executes. No prompt injection can bypass this
  • Start with your highest-risk action types: migrate them from prompts to manifest one at a time — each one adds a deterministic gate the model cannot ignore
  • Facio's HITL primitives are manifest-driven: the policy engine reads the manifest, enforces the rules, and routes to Placet.io for human review — all outside the model's context window

Sources: The policy manifest pattern draws on implementations documented by Permit.io's agent security framework, Cordum.io's deterministic HITL patterns, and production agent governance architectures. The migration path reflects patterns from enterprise authorization policy engines (OPA, Cedar) applied to the HITL domain.