# an agent asking a person (the human slot)

> An agent that needs something only a person can supply does not guess and does not fail — it asks, and its work PARKS. The wait becomes a modelled fact with an assignee and a clock, not a paused process. Hours later the answer arrives as the result of the question it asked, and the same work carries on. Asking is available to any agent running inside a loop, and nothing is declared to enable it.

<!-- id: agent-hitl · area: agent · stability: preview · html: https://osysharp.com/reference/agent/hitl/ -->

## Summary        {#summary}

An agent working on something real will sometimes reach a point only a person can settle. A charge nobody can
explain from the record. A judgement that is somebody's to make. The two obvious ways out are both bad: guessing
produces a confident wrong answer, and failing throws away the work already done.

So it asks. The run **parks**, the question goes in front of a person, and when they answer, the work continues from
exactly where it stopped.

```osy title="the ask that parks — branch and go wait" syntax
var reply = Auditor.Ask(turns);

if (reply.Parked) {
  // `reply.Question` is what it wants to know. Put it in front of somebody.
  goto Waiting;
}
```

…and later, when they have replied:

```osy title="hours later, the answer resumes the same run" syntax
var reply = Auditor.Answer(this.Item, "A client dinner for four — I paid for all.");
```

## Signature      {#signature}

```osy syntax
<agent>.Answer(task, text)      // → AgentReply
```

| | |
|---|---|
| `task` | the task whose run is parked — inside a loop that is `this.Item`. Must be an `AgentTask` or one of your types deriving from it |
| `text` | what the person said, verbatim. It reaches the agent as the answer to the question it asked |
| returns | an `AgentReply`, exactly as [`Ask`](https://osysharp.com/reference/agent/ask/) does — **because a resumed run can park again** |

## Description    {#description}

### Asking comes from the loop, and is declared nowhere   {#capability}
An agent running inside a [loop](https://osysharp.com/reference/agent/loop/) can ask a person. An agent that is not, cannot — the verb is not
offered to it at all.

That is not a restriction, it is the only honest answer. Parking means *"stop here and come back when somebody
replies"*, and coming back is the loop's doing: a loop is checked at compile time to declare a `Waiting` state, so an
agent inside one always has somewhere to park. An agent with no loop has nowhere. Letting it park anyway would
produce work that waits forever — and on every screen, work waiting forever looks exactly like work waiting patiently
for a person.

⚑ So the capability follows from the structure rather than from a flag. There is nothing to switch on, nothing to
forget, and no way to end up with an agent that can ask but cannot be answered.

### Nothing is suspended, and nothing needs to be   {#mechanism}
This is the part worth understanding, because it explains every other rule on this page.

An agent's turn loop is ordinary code — call the model, run the tools it asks for, repeat. It cannot be frozen across
the hours a person takes to reply, and no amount of machinery would change that.

It does not have to be. **An agent's state is its conversation**, and that is already durable:

1. The agent calls the ask verb. That turn is written down **with no result** — an unanswered question is exactly
   what a parked run looks like.
2. Everything stops. The task reads `Waiting`; the workflow holds the wait as a `Slot` with an assignee, an
   opened-at and an SLA clock.
3. The person answers. Their words are written as **that call's result**.
4. A fresh run replays the conversation — the original instruction, the agent's own question, and now its answer —
   and the model carries on as though it had called a very slow tool.

Nothing was suspended, so nothing has to be woken. This is also why an agent that keeps no conversation cannot do
any of it, and why that is refused **at the ask** rather than discovered at the answer.

### An agent that retains nothing is refused when it asks   {#memory-required}
Asking requires `Memory = Persistent` on the agent. Its history *is* its continuation, so an agent with none has
nothing to come back to.

The refusal happens the moment it tries to ask, not later when somebody answers — because a run that parks and can
never be resumed is a dead end, and a dead end is indistinguishable from patience on every screen you would look at.
The message names the one line that fixes it.

### Parking again is ordinary   {#park-again}
`Answer` returns an `AgentReply`, and it can come back parked. An answer that raises one more question is what
conversations do.

Branch on it, or you will move to a terminal state over work that is still waiting on somebody:

```osy syntax
on Answered(string text, Slot slot) {
  var reply = Auditor.Answer(this.Item, text);
  if (reply.Parked) { goto Waiting; }     // it asked one more thing
  goto Completed;
}
```

### What the platform owns, and what you own   {#split}

| | |
|---|---|
| **The platform** | that the run can park at all; that the wait is a `Slot` with an assignee, an opened-at and a clock; that the answer arrives as the question's result; that the same task and the same conversation carry on |
| **You** | who is asked · how they hear about it · what the question looks like on screen · how long they get · what happens if they never reply |

The second column is not an oversight. Any opinion the platform held about who to notify would be wrong in every app
that disagreed — so the `Waiting` state's `enter` body is yours, and so is whatever row you write to put the question
in front of somebody.

### Answering goes through the slot   {#answering}
Deposit the answer into the workflow slot rather than calling `Answer` from a page. The slot is what decides whether
*this* caller may answer, and what records that they did and how long they took — which is what makes *"it waited 15
hours for Sam"* a question with an answer instead of a shrug.

```osy syntax
void AnswerAuditor(Question question, string text) {
  ReviewClaim.For(question.Task).Filer.Answered(text);
}
```

The workflow's own `on Answered` arm is what calls `Answer`. Depositing is server-side because the authority check
is: a browser cannot decide who is allowed to answer.

### What the person is shown   {#question}
`reply.Question` is what the agent asked, in its own words. It is handed back rather than dropped because it is the
whole content of the work item — an inbox that knows an agent is waiting but not what it wants is not an inbox.

Write it somewhere your own screens can read. What that row looks like is yours; the demo keeps a `Question` row
against the claim, and renders it at the top of the page the person is already on.

## Examples       {#examples}

The smallest app in which an agent can ask a person. Everything here exists to make the two marked lines reachable —
the agent's work runs inside a loop, so it may ask; and the answer goes in through the slot:

```osy title="agent-asks-a-person" test app=agent-hitl
using Osysharp.Agents;

[Principal] entity User {
  [Required, MaxLength(255)] string Email;
  security { allow read when IsAuthenticated; }
}

entity Claim {
  [Required, MaxLength(200)] string Title;
  security { allow read, create, update when IsAuthenticated; }
}

/// What the agent asked. The platform parks the run; this row is what a person actually sees.
entity Question {
  [Required] ReviewTask Task;
  [Required, MaxLength(2000)] string Text;
  [MaxLength(2000)] string? Answer;
  security { allow read, create, update when IsAuthenticated; }
}

entity ReviewTask : AgentTask {
  security { allow read when IsAuthenticated; }
}

agent Auditor {
  Purpose   = "Review a claim.";
  Prompt    = "You review expense claims.";
  Memory    = Persistent;                        // required: an agent that retains nothing cannot come back
  Principal = new User { Email = "auditor@example.com" };
  Loop      = ReviewClaim;                       // …and this is what lets it ask at all
}

workflow ReviewClaim {
  Tracks    = ReviewTask.Status;
  Autostart = true;
  Initial   = Running;

  event Answered(string text);

  state Running {
    enter {
      var turns = new List<Turn>();
      turns.Add(Turn.User("Review this claim."));

      var reply = Auditor.Ask(turns);
      if (reply.Parked) {                        // ← IT ASKED SOMEBODY
        Ask(this.Item, reply.Question ?? "");
        goto Waiting;
      }
      goto Completed;
    }
  }

  state Waiting {
    subscribe Answered(string text) as Filer {
      Candidates = u => u.Email != "";
      Finished { Within = TimeSpan.FromDays(2); Unfinished { goto Failed; } }
    }

    on Answered(string text, Slot slot) {
      var reply = Auditor.Answer(this.Item, text);   // ← AND THIS IS THE ANSWER REACHING IT
      Close(this.Item, text);
      if (reply.Parked) { goto Waiting; }
      goto Completed;
    }
  }

  terminal success Completed { }
  terminal error   Failed { Message = "the review could not be completed"; }
}

void Ask(ReviewTask task, string text) {
  var q = new Question { Task = task, Text = text };
  UnitOfWork.Commit();
}

void Close(ReviewTask task, string text) {
  var open = Question.Where(q => q.Task == task && q.Answer == null).FirstOrDefault();
  if (open != null) { open.Answer = text; UnitOfWork.Commit(); }
}

/// What your page calls when the person replies.
void AnswerAuditor(Guid questionId, string text) {
  var question = Question.Where(q => q.Id == questionId).FirstOrDefault();
  ReviewClaim.For(question.Task).Filer.Answered(text);
}
```

The same example with the surrounding app — a real claim, a filer, and the trigger that starts the work — is on
[[agent-loop#examples|the loop page]].

## See also       {#see-also}
- [the agent loop (app.Agent, Loop)](https://osysharp.com/reference/agent/loop/) — the workflow the work runs inside, and what makes asking possible
- [running an agent from your code](https://osysharp.com/reference/agent/ask/) — running a declared agent, and the `AgentReply` both verbs answer
- [Workflow.Work&lt;T&gt; (everything outstanding) and its SLA numbers](https://osysharp.com/reference/workflow/work/) — slots, assignees and SLA clocks: what a wait is made of
- [the agent task log (AgentTask)](https://osysharp.com/reference/agent/task-log/) — the task that stays open while the run is parked
