Step 6 · The human gate
What this step adds: the boundary between recommending and acting — a human approval gate.
The new idea
Section titled “The new idea”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.
The code
Section titled “The code”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.
Run it
Section titled “Run it”uv run step6.pyExpect the recommendation and reason, then the pause:
AGENT RECOMMENDS: FLAGREASON: 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.
Why the gate is code, not prompt
Section titled “Why the gate is code, not prompt”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.
What’s still wrong
Section titled “What’s still wrong”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.