How a One-Line Prompt Becomes an Agent Turn
메뉴

AI Agent

How a One-Line Prompt Becomes an Agent Turn

The boundary where raw user input becomes a controlled agent turn.

How a One-Line Prompt Becomes an Agent Turn hero image

Key takeaways

  • The Enter input is not a model call but a turn scheduling event.
  • The submit boundary branches to empty input, exit command, local command, queued input, and model query.
  • If you don't decide how to handle new input when there is a turn in progress, the runtime state can easily be broken.
  • Direct input and queued input must be normalized to the same internal type.

The topic of this article is the Enter key. To exaggerate a bit, the stability of a product-type AI agent begins with how the Enter key is interpreted.

For the user, all it means is “I typed the prompt and pressed Enter.” However, from the runtime perspective, this event should not be a direct model API call. First, we need to decide whether this input is an empty input, a termination command, a local command, a queue item to be appended to an ongoing turn, or a new model query.

This boundary will be called submit boundary here.

1. Why submit boundary is needed

Suppose the model stream is still in progress and the user enters a new prompt. By sending this input directly to a new model call, two loops can simultaneously modify the same message history and screen state.

The results are usually not good.

  • The second response is mixed in between the first model response.
  • The tool request/result order is broken.
  • It becomes unclear which turn the user-approved task belongs to.
  • The transcript is stored in an unrecoverable order.

Therefore, the submit boundary is a gate that determines “Can it be executed now?”

2. Atomicity in one turn

In agent, turn is not just a simple user message. The turn includes input normalization, model stream, tool request, permission decision, tool result, final assistant message, and cost history.

# How to read: The items below are summarized examples to quickly check the action flow.raw prompt→ normalized turn→ model stream→ tool request→ permission decision→ tool result→ model continuation→ final answer→ ledger flush

If a new turn modifies the same state before this flow completes, atomicity is broken. So the submit boundary should have states likeidle,dispatching,running, andfinishing.

3. Input branching rules

At the submit boundary, the input roughly branches to one of the following:

branchexampleProcessing method
ignoreempty inputDo nothing
local actionhelp, clear, settingsProcess without model call
interruptcancel, stopDeliver stop signal to active turn
queued turnNormal prompt that appears while runningSave to pending queue
model querynormal promptStart model loop after input normalization

If you don't make this branch clear, your command and prompt will get mixed up. In particular, sending a slash-style command as a normal prompt or, conversely, misinterpreting a normal prompt as a local command breaks the user experience.

4. Guard and queue design

The most important thing in submit boundary is guard. A guard protects “whether the current runtime can start a new turn.”

Another important thing is queue. If a new input is unconditionally discarded when there is a turn in progress, usability is bad, and if it is executed immediately, the state will be broken. Therefore, a queue is needed.

componentrole
ExecutionGuardDetermines whether a new turn can be started
PendingTurnQueueKeep input in order while in progress
TurnDraftSave original input, input mode, attachment, source
SubmissionRouterLocal action, interrupt, model query branching

5. Submission flow through concept code

The code below is not the original implementation, but newly written code for explanation purposes.

# How to read: Conceptual code that describes runtime boundaries, not a clone of the actual implementation.class ExecutionGuard:    # Initializes runtime dependencies and state storage that the object will reference in later steps.    def __init__(self):        self.state = "idle"        self.owner = None     # Checks if the current turn can take execution and locks it into the dispatching state.    def reserve(self, turn_id):        if self.state != "idle":            return False        self.state = "dispatching"        self.owner = turn_id        return True     # Converts the turn that is ready for input to the actual running state.    def mark_running(self):        self.state = "running"     # When the turn with execution rights ends, the guard returns to the idle state.    def release(self, turn_id):        if self.owner == turn_id:            self.state = "idle"            self.owner = None # Branches raw input to one of ignore, interrupt, local action, or model turn.async def submit_input(raw_entry, runtime):    draft = TurnDraft.from_raw(raw_entry)    route = classify_submission(draft, runtime)     if route.kind == "ignore":        return     if route.kind == "interrupt":        runtime.abort_current_turn(route.reason)        return     if route.kind == "local_action":        await runtime.local_actions.run(route.action)        return     if runtime.guard.state != "idle":        runtime.pending_turns.append(draft)        runtime.shell.notice("Executes after the current turn ends.")        return     if not runtime.guard.reserve(draft.turn_id):        runtime.pending_turns.append(draft)        return     try:        runtime.guard.mark_running()        prepared = await runtime.input_pipeline.prepare(draft)        await runtime.agent_loop.run(prepared)    finally:        runtime.guard.release(draft.turn_id)        await drain_pending_turns(runtime)

The important part here is thatreserve()is called before input normalization. This is because the normalization process itself can be asynchronous. If another input is received in the meantime, both turns can start simultaneously.

6. AI utilization developer perspective

Developers using AI coding agents must check what policy the tool uses when new input is entered.

situationgood UX
Enter new prompt during model responseShows “Added to queue”
Enter stop during long-term operationSending cancellation signals to running models and tools
Enter local commandProcessed without polluting the model context
Input with attachments queuedAttached snapshots are kept together

In particular, attachments and editor selections must be left as snapshots at the time of queue. If the file state at the time of execution is re-read, it may differ from the user's intended context.

7. Agent developer perspective

When creating an agent, problems quickly arise if direct input and queued input are handled through different code paths. The first prompt you enter will process the attachment, the queued prompt will lose the attachment, and so on.

Therefore, it is recommended to convert both toTurnDraftand then make them pass through the sameexecute_turn().

# How to read: Conceptual code that describes runtime boundaries, not a clone of the actual implementation.def to_turn_draft(text, source, attachments=None, mode="chat"):    return TurnDraft(        id=new_id(),        text=text,        source=source,        attachments=list(attachments or []),        mode=mode,        captured_at=now(),    )

Additionally, you must distinguish between the context scope of the first input and subsequent queued input. Context pollution can occur if you blindly attach rich context, such as editor selections, current diffs, or temporary attachments, to all queued inputs.

8. Practical checklist

# How to read: The items below are summarized examples to quickly check the action flow.Submit Boundary Checklist [ ] Enter input does not immediately lead to a model API call.[ ] Input branches to local action, interrupt, queue, and model query.[ ] When there is an active turn, the new input processing policy is clear.[ ] Direct input and queued input use the same TurnDraft type.[ ] guard reservation occurs before asynchronous normalization.[ ] Attachments and editor context are preserved as snapshots when needed.[ ] There is logic to drain the pending queue after the turn ends.

Failure case: When Enter immediately makes a model call

Without a submit boundary, even the simplest input window can create complex bugs. Let's say the user sends the first prompt and enters a second prompt while the model response is being streamed. If the runtime connects Enter directly tocreateModelCall(), two calls read and write the same history at the same time. The second call starts before the tool result of the first call has come in, the two responses are mixed on the screen, and the cancel button doesn't know which call to stop.

This problem may not be visible in testing. This is because happy paths that are entered only one at a time will pass. Actual users do not wait and add additional comments, cancel incorrectly sent prompts, or enter local commands such as/status. Submit boundary does not consider all of these inputs as the same event, but branches to an action that matches the current runtime state.

Implementation example: submit decision table

current statusInput exampledecisionreason
idlenormal promptstart_turnCan start a new model turn
idle/statusrun_local_commandNo need to call model
streamingnormal promptenqueue_turnProtect active turn history
streamingEscapecancel_active_turnControl input, not a new turn
awaiting_approvalOK buttonresume_turnRelease pending status of existing turn

This table is not a UI document but a runtime contract. The input window, command parser, and model loop must share the same decision. In particular, local commands should not enter the model context, and queued prompts should only have the necessary context snapshots. Otherwise, when the user says, “Continue based on the code I selected earlier,” there will be a problem of reading the editor selection that has already changed.

Checklist application results

When reviewing submission boundaries, we check three things: First, see if we schedule an active turn guard before input normalization. Second, check whether the queued input passes the sameTurnDrafttype as the direct input. Third, check that cancel and local commands do not pollute the model message history. If these three things are correct, the agent will not lose its order even if the number of inputs increases.

finish

The Submit boundary may seem small, but it is the central boundary of the agent runtime. With this layer, input, command, queue, cancel, and model loop will not conflict with each other.

In the next article, we will see how raw input that passes the submit boundary is converted into a model-visible message. This process is input normalization.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP