camelRun by camelAI

Human in the
loop
AI agents

camelRun by camelAI is a hosted runtime for AI agents that ask a person before a risky step, sleep with no agent time billed while they wait, and carry on from the same step when the answer comes.

Agentops
completed
Fri23 Oct
Sat24 Oct
Sun25 Oct
Mon26 Oct
Mon 09:12Running · billedAsleep · storage only
RunningAsleep
aliceFri 17:58

Delete the staging database

opsAgent

Allow db__drop to run?

{"database":"staging"}
Approved by alice· Mon 09:12
opsAgentMon 09:12

Done. The staging database is gone.

input_resolved · answered · by alice

→ db__drop({"database":"staging"})

opsAgentMon 09:12

Done. The staging database is gone.

Four kinds of input

How AI agents ask a person

Approval is one of four ways an agent can ask. It can also ask a question, show a short form, or send someone to a web page to finish a step. Each one pauses the run the same way.

  1. 01Approval

    Allow db__drop to run?

    {
      "database": "staging"
    }
    ApproveDeny

    Approve a tool call

    Mark a tool needsApproval, or give an MCP or OpenAPI source a policy. The person sees the exact call before it runs.

    needsApproval: true

  2. 02Question
    Snapshot

    Keep a snapshot of staging before it goes?

    • Keep one
    • No snapshot
    Submit

    Answer a question

    Turn on the ask_user tool and the model can ask multiple-choice questions, with room for the person's own words where it allows.

    "builtins": ["ask_user"]

  3. 03Form

    Where should the last backup go?

    bucketops-archive
    labelstaging-final
    SubmitSkip

    Fill in a short form

    A tool calls context.ask with a flat form, and the person's answers come back to it as fields.

    context.ask(message, schema)

  4. 04Web page

    Connect the cloud account to finish

    Open https://accounts.example.comDoneCancel

    Finish a step on a web page

    A tool calls context.requireUrl to send the person to an https page to connect an account or sign something.

    context.requireUrl(url, message)

Approvals in code

Add a human approval step to your AI agent

When a step needs a person, the run comes back with status input_required and the input it waits on. Answer it now or days later, from this process or another one, and you get the resumed run.

Per tool
needsApproval: true asks before every call.
Per call
A function of the call's arguments decides each time.
Per source
MCP and OpenAPI tools take never, always or destructive.
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
import { basename } from "node:path";
import { createInterface } from "node:readline/promises";
import { Agents, schema, tool } from "@camelai/run";

// A throwaway folder for the agent to tidy.
const dir = "playground";
await mkdir(dir, { recursive: true });
for (const f of ["notes.md", "build.log", "cache.tmp", "old-draft.tmp"]) await writeFile(`${dir}/${f}`, "x");

const agents = new Agents();
const agent = await agents.upsert("approval-ts", {
  model: "openrouter/openai/gpt-6-luna",
  instructions: "You tidy the user's playground folder with your tools.",
  tools: {
    list_files: tool({
      description: "List the files in the playground",
      input: schema.Object({}),
      execute: () => readdir(dir),
    }),
    delete_file: tool({
      description: "Delete a file from the playground",
      input: schema.Object({ name: schema.String() }),
      needsApproval: true, // the run stops and waits for a person before each call
      execute: async ({ name }) => (await rm(`${dir}/${basename(name)}`), { deleted: name }),
    }),
  },
});

const cli = createInterface({ input: process.stdin, output: process.stdout });
let run = await agent.run(process.argv.slice(2).join(" ") || "Delete the temporary files.", { user: "me" });
while (run.status === "input_required") {
  const input = run.inputs[0];
  const yes = (await cli.question(`${input.message} ${input.detail.argumentsPreview ?? JSON.stringify(input.detail.arguments)} [y/N] `)).trim() === "y";
  run = await input.answer(yes, { from: "me" });
}
console.log(run.text, "\nleft:", await readdir(dir));
cli.close();
await agents.close();
approval-ts/index.tsDocs
Terminal
npm install
export CAMELAI_API_KEY=art_...
npm start
Allow delete_file to run? {"name":"cache.tmp"} [y/N] y
Allow delete_file to run? {"name":"old-draft.tmp"} [y/N] n
Deleted `cache.tmp`. I couldn't delete `old-draft.tmp` because that deletion was declined.
left: [ 'build.log', 'notes.md', 'old-draft.tmp' ]

Answer anywhere

Answer AI agent approvals in your app, in Slack or from your server

The waiting input lives in camelRun, not in the process that started the run. Answer it wherever your people are, and the turn picks up the same way.

  • AgentChat

    alice

    Delete the staging database

    Staging has no traffic this week. I'll drop it once you say so.

    Allow db__drop to run?

    {
      "database": "staging"
    }
    ApproveDeny
    Your app

    The React <AgentChat> shows the call with Approve and Deny buttons.

  • Slack · thread

    Thread

    ops

    alice5:58 PM

    @ops Delete the staging database

    opsApp5:58 PM

    Staging has no traffic this week. I'll drop it once you say so.

    Approve this action? db__drop (db) with {"database":"staging"}

    Reply approve or deny.

    alice9:12 AM

    approve

    opsApp9:12 AM

    Done. The staging database is gone.

    Slack, Telegram or Discord

    The agent's reply ends with the request. Someone allowed to answer replies approve or deny.

  • Webhook · REST
    To your server

    POST https://example.com/hooks/agents

    {"id": "evt_…",
     "type": "input.requested",
     "created": 1790000000,
     "data": {"agentId": "client_…",
       "requestId": "…",
       "inputId": "…",
       "toolCallId": "…",
       "kind": "approval",
       "expiresAt": …}}
    Your answer

    POST /v1/agents/:id/inputs/:inputId

    {"action": "accept",
     "from": "alice"}

    The turn resumes.

    Your server

    A webhook tells your server when an agent asks. Answer over the API, and build one approvals inbox across every agent with agents.runtime.inbox.

Human in the loop authorization

Control who can approve your AI agent's actions

Human in the loop authorization decides who gets to say yes.

  • Runs as approved

    An approved call runs with exactly the arguments the person saw.

  • Declined means never

    A declined call never runs.

  • Your tool can check

    The approval travels with the call, on context.identity.approval.

Who may answer
db__drop
  • aliceStarted the turnCan answer
  • slack:U0123An approver in the definitionCan answer
  • samNot on the list403
  • The modelHas no way to answerCan't answer

FAQ

Questions about human in the loop AI agents

  • 01

    What are human-in-the-loop agents?

    AI agents that stop and ask a person before they act on something that matters, like deleting data or changing production. On camelRun the agent pauses at that step, waits for the answer, and then carries on with it.

  • 02

    What is human in the loop authorization for AI agents?

    It's the rule for who may say yes to an agent. On camelRun that's whoever asked for the work plus a list of approvers you keep, and a request from anyone else gets a 403. The model itself can't answer.

  • 03

    What is human in the loop when using AI?

    A person reviews or decides at the points where the AI shouldn't act alone. For an agent that can mean approving a tool call, answering a question, filling in a form, or finishing a step on a web page, while the agent waits.

  • 04

    How long can an AI agent wait for a person on camelRun?

    Days. The run suspends and the agent sleeps until someone answers, and the agent's definition sets how long an input waits. When the answer comes, it picks up from the same step, even after a deploy or a crash in between.

  • 05

    Do I pay while my agent waits for approval?

    No agent time is billed while it waits, because the agent is asleep. camelRun charges $0.01 per agent-hour only while an agent is in a run, and idle and sleeping agents cost only storage.

  • 06

    Can people approve agent actions from Slack, Telegram or Discord?

    Yes. In Slack, Telegram or Discord the agent's reply ends with the request as text. The next message from someone allowed to answer settles it: approve or deny, an option's number, or done after a web page step.

  • 07

    What if someone sends a new message instead of answering?

    The new message wins. The waiting inputs close with "Not answered: the user sent a new message instead", and then the agent takes the new message.

Put a person in the loop today

Get an API key, mark the risky tool, and let your agent ask. The human input guide has the rest.

Set up with your coding agent

Read https://run.camelai.com/SKILL.md and set up camelRun in this project. Ask me what the agent should do.

Paste into Claude Code, Codex, Cursor or any coding agent. It asks what your agent should do, then builds it.