Skip to content
astorlm
← Map

Level 13

Human in the loop

The agent does the work; a person keeps the say. They approve what can’t be undone, correct it mid-way, and answer when it’s unsure.
1/33 Bandoneón folds:
  • user
  • assistant
  • tool_result
  • tool_result (error)
A claw machine at the arcade. The Oracle sits in the fortune-teller booth, Astor works the controls, and Tina, the person in the loop, watches through the glass. One coin left.

EventBus

The problem

An agent that runs without a person is fast right up to the moment it does something nobody wanted: pays the wrong invoice, emails every customer, spends the last coin on the wrong prize. And an agent that stops to ask about everything is no faster than doing it yourself.

That’s Autopilot Rex: never asks, never waits, never takes a call. It commits to its first plan, ignores what changed since, and guesses when it should have asked.

The solution

Put a person at the few points where their judgment matters, and let the agent run everywhere else. There are three such points, and they differ in who breaks the silence:

  • Approve

    The agent waits

    Before an irreversible tool runs, a hook shows the person exactly what it will do and waits for a yes or a no.

    Payments, deletions, messages to customers, the last coin: anything you can’t take back.

  • Steer

    The person interrupts

    The person queues a correction whenever they like. At the next tool call the loop cancels that call and gives the model the correction instead.

    Someone is watching and changes their mind, or sees the agent heading the wrong way.

  • Escalate

    The agent asks

    A tool like ask_human that doesn’t return until a person answers. The model decides when to use it.

    Missing information, two equally good options, low confidence. The system prompt says when to ask.

Approving and steering both live in the beforeToolExecution hook from level 6: it runs before every tool, and it can wait. For an approval it waits for the person’s answer; for steering it checks whether a correction is queued. Either way, what the person said goes back to the model as the tool’s result, so the run goes on with the new information instead of crashing.

The cast

Same cast as always, at the arcade this time.

The fortune teller the model
The Oracle, in its booth. It reads the history and decides the next call. It never touches the machine.
The controls the tools
move_claw is free and can be undone. drop_claw spends the last coin: it can’t.
Tina the person
Her bubble says who spoke first: ! she interrupted, ? she was asked, YES! she approved.
The screen the approval
DROP? YES NO: the claw hangs still while the hook waits. Nothing runs until she decides.

In the EventBus panel, steering shows up as a user_steering event, followed by the cancelled call’s result. Approvals and answers have no event of their own: they’re a hook and a tool that took their time.

The code

With astorlm: An approval hook for the irreversible tools, wrapped by createSteeringController, which adds the steering queue: call steer(text) from your UI and it lands at the next tool call. Escalation is an ordinary tool that awaits your UI.

From scratch: Three checks in the tool step of the loop from level 2: a queued correction, an approval for the risky tools, and a tool that waits for a person.

import { OpenAIProvider, createLocalAgent, createSteeringController, tool } from 'astorlm'
import { z } from 'zod'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = { baseURL: 'http://localhost:11434/v1', apiKey: 'YOUR_API_KEY' } // local servers usually ignore the key

// Your UI: each of these resolves when the person clicks or types.
declare function approve(what: string): Promise<boolean> // shows YES / NO
declare function answer(question: string): Promise<string> // shows a text box

// 1. ESCALATE: a tool the model calls when it isn't sure. It waits for a person.
const askKid = tool({
  name: 'ask_kid',
  description: 'Ask Tina when you are not sure what she wants. Waits for her answer.',
  schema: z.object({ question: z.string() }),
  execute: async ({ question }) => answer(question),
})

// 2. APPROVE: irreversible tools wait for a yes before they run.
const NEEDS_APPROVAL = new Set(['drop_claw'])
const steering = createSteeringController({
  beforeToolExecution: async ({ toolName, input }) => {
    if (!NEEDS_APPROVAL.has(toolName)) return { authorize: true }
    const ok = await approve(`${toolName}(${JSON.stringify(input)})`) // show the real call
    return ok ? { authorize: true } : { authorize: false, mockResult: 'Tina said no. Ask her what to do.' }
  },
})

const agent = await createLocalAgent({
  provider: new OpenAIProvider({ ...LLM, model: 'your-model' }), // e.g. 'llama3.1', 'gpt-4o-mini'
  systemPrompt: 'You work a claw machine for Tina. If you are not sure which prize she means, ask her before acting.',
  tools: [moveClaw, dropClaw, askKid], // moveClaw, dropClaw: your code
  hooks: steering.hooks, // the steering controller wraps the approval hook
  maxTurns: 20,
})

// 3. STEER: the person can correct the agent at any moment, e.g. from a button.
// The loop cancels the next tool call and hands the model this feedback instead.
onTinaShouts((text) => steering.steer(text)) // your UI: 'No, wait! The penguin!'

const result = await agent.run('Get me the bear!')
console.log(result.content)

What to watch

  • Ask for less, and it means more. Approve only what’s irreversible or expensive. A person who clicks YES twenty times an hour stops reading, and the approval becomes a formality.
  • Show the real call. “Drop?” isn’t enough. Show the tool and its exact arguments, the amount, the recipient, the prize, so the person approves what will actually run.
  • Decide what happens when nobody answers. A person can walk away. Set a timeout, and pick the safe default: usually deny, and tell the model why.
  • Corrections arrive at the next tool call. Steering can’t stop a tool mid-run or a reply mid-word. If the agent is only writing text, the correction waits. For a hard stop, abort the run.
  • Keep a record. Log who approved what, when, and what they saw. When something goes wrong, “the agent did it” is never the whole story.