Skip to content

Step 6 · The human gate

What this step adds: the boundary between recommending and acting — a human approval gate.

Human-in-the-loop: the agent’s output is a recommendation, and a person authorises the consequential action. This decouples two things that must never be coupled in a serious system: how good the model’s judgment is and how bad the consequences of an error are. With a gate, the model can be wrong and nothing bad happens.

Create step6.py:

import ollama
def get_transaction(txn_id):
records = {"T-1002": {"amount": 4200, "country": "Nigeria", "card_present": False}}
return records[txn_id]
def decide(txn):
if txn["amount"] > 1000 and not txn["card_present"]:
return "FLAG"
return "ALLOW"
schema = {
"type": "function",
"function": {
"name": "get_transaction",
"description": "Look up a transaction's amount, country and card_present flag by its id.",
"parameters": {
"type": "object",
"properties": {"txn_id": {"type": "string"}},
"required": ["txn_id"],
},
},
}
messages = [
{"role": "system", "content": "You are a fraud-triage assistant. Explain briefly. Never block a card yourself; only a human can approve."},
{"role": "user", "content": "Review transaction T-1002."},
]
recommendation = "ALLOW"
while True:
response = ollama.chat(model="qwen2.5:3b", messages=messages, tools=[schema])
messages.append(response.message)
if response.message.tool_calls:
for call in response.message.tool_calls:
txn = get_transaction(call.function.arguments["txn_id"])
recommendation = decide(txn)
messages.append({"role": "tool", "content": str(txn)})
else:
print("AGENT RECOMMENDS:", recommendation)
print("REASON:", response.message.content)
break
answer = input("\nApprove this recommendation? (yes/no) ")
if answer == "yes" and recommendation == "FLAG":
print("ACTION TAKEN: card blocked.")
else:
print("ACTION TAKEN: none.")

Two new things:

  • A system message — standing instructions that apply to every turn. Note what it says: never block a card yourself. Useful context for the model’s prose, but read on for why it isn’t the real control.
  • input() — the program stops dead until a person types. That blocking pause is the gate, in its simplest possible form.
Terminal window
uv run step6.py

Expect the recommendation and reason, then the pause:

AGENT RECOMMENDS: FLAG
REASON: The transaction is high-value and card-not-present...
Approve this recommendation? (yes/no)

Try it twice — approve once, decline once. Only yes and a FLAG recommendation produces the block.

Look at where the safety actually lives. The system prompt asks the model not to act — but the model couldn’t act anyway, because the action line sits after input(), outside anything the model touches. The model produces words; the block happens only in your if statement, only after a human typed yes.

Check the default direction too: any answer other than exactly yes results in no action. Mistype, hit enter, walk away — nothing happens. Inattention must never read as consent.

This is the same principle as step 5, applied to actions instead of decisions: the prompt shapes behaviour; the code guarantees it.

A decision was made and approved — and there’s no record of it. Who approved what, based on which data, when? If the answer lives in a terminal scrollback, it doesn’t exist. The final rung is the audit log.

Step 7 — the audit log →