Inside a Coding-Agent Turn

OpenClaude's provider abstraction becomes concrete inside one coding-agent turn. This chapter follows a task from prompt preparation to streamed model output, tool execution, permission decisions, continuation, and saved conversation state. The goal is to show why OpenClaude behaves like an agentic work loop rather than a chat window, and why the route selected in the previous chapter affects what can happen at each step.

From prompt to executable work

Consider a request such as: “Find the failing test, make the smallest safe fix, and run the test again.” OpenClaude cannot complete that request from the text of one model response. It needs a sequence of state changes. The model must receive enough system and project context to understand its role. It must know which tools are available. Each proposed action must pass the tool and permission boundaries. The result of an action must return to the conversation so the model can decide what to do next.

The QueryEngine is the component that holds this conversation-level state. One engine represents one conversation, and each submitted message starts a new turn within it. The engine retains messages, a file-read cache, usage, abort state, permission denials, and compaction tracking across turns. It also receives the working directory, tools, commands, connected MCP clients, agent definitions, model choice, and permission callback needed to process the current request. This ownership matters: a later turn can depend on what was read, which files changed, which model is active, and which permissions were denied earlier.

Before the provider is called, OpenClaude prepares the request context. It combines the default or custom system prompt with user and system context, processes slash commands, and turns the incoming prompt into message objects. A slash command may change local state or return local output without requiring a model call. Otherwise, the engine loads the available skills and cached plugins, builds an initialization message that describes the active environment, and enters the shared query loop. The model is not yet editing anything; it is being given the controlled surface through which editing could occur.

The model response is streamed. As text and tool-use blocks arrive, OpenClaude can surface progress instead of waiting for a complete response. A tool-use block is a structured request such as reading a file, running a command, editing a file, invoking an MCP tool, or starting an agent. The tool contract includes an input schema, a permission check, a read-only or destructive classification, a concurrency decision, and methods for reporting progress and results. This means a tool is not just a function name exposed to the model. It is an operation with a known input shape and a defined relationship to user approval and other work.

A useful distinction appears here. The model proposes; OpenClaude executes conditionally. The model may request a file edit, but the permission layer decides whether the path and operation are allowed. The model may request two independent reads, but the executor decides whether they can safely run in parallel. The model may call an MCP tool, but that tool must be connected and not filtered by the current permission rules. The agent's practical capability is therefore the intersection of model competence, available tools, connected services, and policy.

sequenceDiagram participant U as User participant Q as OpenClaude query engine participant P as Provider transport participant X as Tool executor participant A as Permission layer participant T as Transcript state U->>Q: Submit coding task Q->>Q: Build context and available tools Q->>P: Send prompt and tool schemas P-->>Q: Stream text and tool-use blocks Q->>X: Queue requested tool X->>A: Check operation and permission A-->>X: Allow, ask, or deny X-->>Q: Return tool result or bounded error Q->>T: Record assistant and tool messages Q->>P: Continue with tool result P-->>Q: Stream final response or more tools Q-->>U: Stream progress and result

The tool executor can begin work while the model is still streaming its response. OpenClaude's streaming executor tracks each tool as queued, executing, completed, or yielded. A concurrency-safe operation may run alongside another concurrency-safe operation. A non-concurrent operation requires exclusive access, because parallel execution could create a race over files, the working directory, or another shared resource. Results are buffered and emitted in the order the tool calls were received even when safe operations finish at different times.

That ordering solves a structural problem as well as a display problem. A model's assistant message contains tool-use identifiers, and the next user-side message must contain matching tool results. If an interruption or sibling failure cancels a call, OpenClaude creates a synthetic error result instead of leaving an unmatched request in the conversation. If a streamed attempt is abandoned because the request must fall back to another model, pending tool execution is discarded and its results are prevented from leaking into the replacement attempt. Without these repairs, a technically valid retry could inherit an invalid message sequence.

Permissions remain in the middle of this process, not at its edge. OpenClaude's permission rules can allow, deny, or ask about tools and paths. The tool contract distinguishes read-only operations from writes and destructive actions, while the path checks consider working directories, sensitive locations, symlinks, and sandbox configuration. MCP tools can also be filtered by deny rules before the model sees them. A model that is capable of using a tool does not thereby receive permission to use it; a user-approved operation can still be constrained by path or safety checks.

The result is a loop with a clear causal shape:

  1. The user states a task.
  2. OpenClaude builds the current context and tool surface.
  3. The selected transport sends the request to a model.
  4. The model emits an answer, a tool request, or both.
  5. OpenClaude validates and authorizes each requested operation.
  6. Tools return results, errors, or progress.
  7. The results re-enter the conversation for another model pass.
  8. The loop ends when the model produces a terminal answer, a hook stops continuation, the user interrupts, or a safety or resource limit blocks further work.

Take the example request one step further. The model might first ask to search for the test name and read two candidate files. Those reads can be independent, so the executor may run them at the same time if their tool definitions mark them as concurrency-safe. The model then receives both results together in the order OpenClaude assigns to the tool calls. It can use that evidence to request an edit, followed by a test command. The edit and the test should not be treated like two unrelated reads: the test must observe the completed edit, and the permission layer may ask the user before the write or command occurs.

A denial creates another valid branch rather than ending the conversation with an unexplained absence. OpenClaude returns a tool result that tells the model the operation was not carried out, allowing the model to revise the plan or explain the blockage. If the user interrupts while a shell command or file operation is in progress, the executor checks the operation's interruption behavior. Some tools can be canceled; others are allowed to finish or are represented by a synthetic error result. In both cases, the next model request must know that the action did not produce an ordinary success result.

This is also why the engine separates messages shown to the user from the internal conditions that make a continuation legal. Streaming text may appear before the model has finished choosing tools. Progress messages may describe an operation while its final result is still pending. A compact boundary may replace older history without being the same thing as a user message. Preserving those distinctions lets the query loop resume from a meaningful state instead of flattening every event into text.

The sequence is stable across many routes, but the quality of each transition is not. A model with weak function calling may produce malformed or poorly chosen tool requests. A route may support text generation but not the reasoning or image inputs a particular task requires. A local model may respond correctly to a short read request but lose coherence after many tool results. OpenClaude can enforce the loop's structure; it cannot make every model equally good at participating in it.

What persists when the turn continues

The most important state is not the text of the last answer. It is the relationship among messages, tool results, files, permissions, and context limits. After a tool result arrives, the query loop can continue using the updated conversation. The engine records assistant messages, user-side tool results, compact boundaries, progress messages, and attachments. It tracks usage and stop reasons for the current response and can expose partial stream events to SDK consumers.

Transcript persistence begins earlier than a simple chat implementation might expect. OpenClaude writes accepted user messages before entering the provider loop so that a session can remain resumable even if the process is killed before a model response arrives. As assistant, tool, progress, and compact-boundary messages appear, they are recorded in order. The implementation also flushes pending writes before emitting a terminal result in contexts where a client may immediately terminate the process. These details support resume behavior, but they do not guarantee that a resumed conversation will retain every nuance of its original context.

Interruptions are state transitions, not merely UI events. If a user stops generation while tools are running, the executor must decide which operations can be canceled and which must block. Aborted tools receive synthetic results so the next request does not contain an orphaned tool call. OpenClaude also performs cleanup for operations that hold resources or change the terminal state. The visible outcome may be “interrupted,” but the underlying conversation still needs a structurally valid boundary before the user can continue or resume it.

Long conversations create a second pressure: the model request cannot grow without limit. OpenClaude uses token-aware compaction and also has message-count safeguards for sessions that accumulate many small messages. The advanced setup documentation gives a default proactive message-count threshold of 200 and a hard active-message cap of 1000. Those values describe OpenClaude's safety defaults, not a provider's context window. When context overflows or a provider rejects an output size, the query loop can compact, reduce, retry with a provider-reported cap, or stop with an actionable error. A compaction boundary changes what history is sent to the next request; it is a controlled loss of detail, not an invisible extension of memory.

The local Ollama path shows why route metadata and lifecycle state meet in practice. OpenClaude uses Ollama's native chat API for its endpoint and requests a 32768-token context window by default, with an environment setting for users who need another request-level size. The server still needs to be running with a model loaded, and the model and hardware must handle the requested work. A larger requested window can preserve more history, but it cannot compensate for weak tool use or insufficient memory. Context is one operating constraint among several.

Compaction and retries also have to preserve message validity. Tool results must remain paired with the assistant tool calls they answer. Provider-specific thinking blocks may need to be kept or removed according to the transport. A fallback model may not accept the previous model's reasoning signature. The query loop therefore treats recovery as a rewrite with rules: it may remove or summarize history, strip incompatible content, and retry, but it must not blindly replay bytes that belong to a different route or model.

There are several ways for a turn to end normally. The model may provide a final text response after the last tool result. A hook may deliberately stop continuation. A maximum-turn or budget limit may produce a bounded error. The user may interrupt. A repeated tool-failure guard may stop a loop that is repeating the same failed operation. A provider may return an error that cannot be repaired by compaction or fallback. These are different terminal states, and OpenClaude records different messages and result metadata so callers can distinguish them.

This lifecycle explains why portability has to be judged at the level of work. The same route choice that looks successful for a one-shot explanation may fail when the task needs several reads, an edit, a shell command, and a long continuation. The model, transport, tool surface, permission policy, and context budget all participate. Once a user understands that chain, choosing a route becomes a decision about the work it must sustain—not merely a decision about which name appears in the model picker.

References