We will follow one task throughout the article. A user writes in Telegram: “Check the failing project tests, fix them, and send the result back here.” The sentence is short. The runtime work is not. OpenClaw must decide whether the event may start an agent turn, which durable session owns it, whether that session already has a run in flight, what the model is allowed to see and do, and how text or media should return to Telegram.

Collapsing all of that into “receive message → call model → send answer” makes three common failures impossible to explain. Why can the same human share context across some chat surfaces but not others? Why can a caller still be waiting after assistant text has appeared? If a tool changed a file and final delivery failed, should the system replay the input, resume a run, or only retry delivery?

This opening chapter therefore stays on one inbound turn. It connects the main call path, the state owners, and the terminal signal. Later chapters zoom into each boundary rather than repeating a shallow overview of every feature.

Reading contract.By the end, you should be able to explain why a provider event first becomes normalized context; why sessionKey constrains behavior before model selection; how model and tools hand control back and forth; why the assistant, tool, and lifecycle streams are not interchangeable; and why ReplyPayload is an outbound value rather than the state of the run.

Evidence boundary.This article pins OpenClaw at commit c549250bfae8ff40822099c9af93ed05ff540579. Names and call order come from that snapshot; product-level contracts are checked against the official Agent Loop, Messages, and Session docs. Source can establish structure and ownership. It cannot, by itself, prove latency, reliability, or security properties in every deployment.

1. A turn is larger than a model request

Our user sent one message, but the agent may contact the model four times: once to decide to read the test log, again to search the relevant code, again after editing to run the tests, and finally to explain the verified result. A turn is not one model request. It is the lifecycle from inbound admission through a run-level terminal state and outbound handling.

This distinction puts tool execution in the right place. A tool is not an optional postscript after the “real” model work. Its result becomes evidence for the next model decision. The turn remains alive while the runtime can still produce another tool call, request interaction, compact and retry, or enter a recoverable branch.

ObjectWhat it ownsWhat it does not own
MessageA channel event and its text, media, sender, and thread facts.It does not inherently choose a session or start an agent.
Model requestOne runtime view sent to one provider/model candidate.It is not the whole turn and does not own delivery.
Agent turnAdmission, session, queues, model, tools, events, termination, and reply handling.It need not contain exactly one request or only text output.
SessionDurable identity, history ownership, and a concurrency boundary across turns.It is neither the active Promise nor the final reply.

2. A message enters the channel kernel, not the model

Telegram, Discord, Slack, and WebChat deliver very different raw objects. OpenClaw keeps those provider details at the plugin and adapter boundary. The shared runChannelTurn first asks the adapter to ingest a raw event into normalized input, then classifies whether that event may begin an agent turn.

Receiving an event is therefore not the same thing as running an agent. A channel-owned interaction, an observe-only event, or a rejected admission can end before any model work. The kernel even has a policy-controlled history path for dropped events. Admission is itself a fact worth recording; the absence of a reply is not enough to tell an operator what happened.

const input = await params.adapter.ingest(params.raw);
const eventClass = (await params.adapter.classify?.(input)) ?? DEFAULT_EVENT_CLASS;

if (!eventClass.canStartAgentTurn) {
  return { admission: { kind: "handled", reason: `event:${eventClass.kind}` }, dispatched: false };
}

The transferable rule is simple: translate provider-owned events into product-owned inputs before applying shared admission and lifecycle policy. Otherwise every channel reimplements deduplication, audit, history, and error semantics—and every fix becomes a partial fix.

3. Routing produces a sessionKey before model selection

Once admitted, the message needs an owner. OpenClaw routing produces more than an agent name. It contributes to a stable sessionKey that can incorporate channel, account, peer, thread, binding, and agent configuration. Messages with different keys belong to different state domains even if both later use the same provider and model.

That is why model selection is not the earliest important decision. A provider/model candidate can fail over during a run. Session identity must already exist so queues, persistent state, and the reply operation have something stable to own. The signature and setup of runReplyAgent carry sessionEntry, sessionStore, sessionKey, queueKey, and resolved queue policy alongside model information.

Debugging shortcut.When “the same user” appears to have lost context, compare route resolution and sessionKey before changing prompts or memory. Many apparent context failures were already split into separate sessions upstream of the model.

4. Queues protect causal history, not only throughput

Suppose the user sends “fix the tests” and immediately follows with “do not edit generated files.” If both turns read the old transcript and write independently, the second constraint can land after the first tool action—or two runs can mutate the same workspace concurrently. OpenClaw uses both a session lane and a global lane to keep these concerns separate.

In runEmbeddedAgentOrchestrated, a lane controller exposes enqueueSession and enqueueGlobal. The code enters the session lane, waits for prior deferred transcript maintenance for that session, and only then enters the global concurrency gate.

  • The session lane protects ordered reads and writes within one state domain.
  • The global lane limits expensive concurrent runs across the process without turning one busy session into a lock on every other session.

Queue modes therefore change semantics, not just performance. Steering, following up, interrupting, and collecting are different answers to one ownership question: may a new message join the active run, must it become the next author of the transcript, or should it replace the run? Part three will trace those modes in detail.

5. dispatchReplyFromConfig is a staged, short-circuiting pipeline

With route and session prepared, the request reaches dispatchReplyFromConfig. The name sounds like a thin configuration switch. The inner function instead advances through request gathering, delivery preparation, operation context, operation, route, execution, finalization, and audit.

const gathered = await gatherDispatchRequest(params, messageAuditTerminal);
if (gathered.status === "complete") return gathered.result;

const delivery = await prepareDispatchDelivery(gathered.state);
const context = await prepareDispatchOperationContext(delivery.state);
const operation = await prepareDispatchOperation(context.state);
const route = await chooseDispatchRoute(operation.state);
const execution = await prepareDispatchExecution(route.state);
const executed = await executeDispatch(execution.state);
return (await finalizeDispatchAndAudit(executed.state)).result;

The repeated status === "complete" checks matter. A command handler, policy decision, or no-model response can terminate in its own stage without pretending an embedded agent succeeded. On error, the function commits or releases its inbound dedupe claim according to replay safety, records the dispatch and processing outcome, and returns the runtime to an idle state. “Can we retry?” is already being shaped by possible side effects.

6. runReplyAgent is the seam between channel semantics and the runtime

Branches that require a model eventually enter runReplyAgent. This layer knows about typing indicators, block streaming, reply threading, partial replies, session state, queue policy, model resolution, and tool progress. It is not merely a model wrapper. It turns internal run events into progress and payloads that a channel can consume.

The same seam checks duplicate restart-recovery sources before model execution. It reloads the durable session entry, compares the source turn id with an existing claim, and can retire the claim or return without redoing work. That check belongs here: earlier layers do not yet have enough durable run ownership, while a later check could repeat tool side effects.

Only now does our test-fixing request have permission to execute. Everything before it established ownership, ordering, delivery, and replay constraints.

7. The model–tool loop lives inside runEmbeddedAgentInternal

runEmbeddedAgent is a plugin-facing JavaScript boundary. It strips host-only fields before calling runEmbeddedAgentInternal. The internal entry captures a lifecycle generation, resolves configuration, and enters the orchestrator. Inside the lanes, the orchestrator resolves workspace, agent directory, model candidates, and harness runtime.

“Embedded” does not mean “a stateless call.” It means the agent harness runs inside the OpenClaw process. Before an attempt starts, the runtime still assembles workspace and bootstrap files, system prompt, skill snapshot, governed tools, session transcript, and model candidates. Parts four through six will separate those inputs and their policy owners.

while (!terminal) {
  const assistant = await model.respond(runtimeView);
  emit("assistant", assistant);

  if (!assistant.toolCalls.length) break;
  for (const call of assistant.toolCalls) {
    emit("tool", { phase: "start", call });
    const result = await governedToolRunner.execute(call);
    emit("tool", { phase: "end", call, result });
    runtimeView.append(result);
  }
}

This is pseudocode, not a pasted implementation. Its ownership is the important part: the model proposes structured tool calls; the governed runner performs real actions; results re-enter the runtime view for another model decision. That is why the Tool arrow in the figure loops around runEmbeddedAgentInternal, not around channel ingress or ReplyPayload.

8. Three event streams carry three different facts

OpenClaw embedded-agent assistant, tool, and lifecycle streams; only lifecycle end or error releases agent.wait

A caller cannot wait until the final answer to observe a long run. OpenClaw subscription handlers separate assistant, tool, and lifecycle streams. They may occur close together, but they answer different questions.

StreamFact representedTypical consumerWhy it is not a run terminal
assistantText and message content the model is producing.Streaming UI, partial delivery, transcript assembly.Text can be followed by a tool call.
toolA tool call started, progressed, or ended and produced a result.Progress, audit, media, and side-effect tracking.Another tool or model response may follow.
lifecycleThe run started or reached an end/error terminal.agent.wait, registries, recovery, terminal delivery.It is the run-level terminal protocol.

handleAgentStart emits a lifecycle start. handleAgentEnd cannot simply check whether the last assistant message contains text. It also considers deterministic side effects, message-tool delivery evidence, accepted session spawns, cron creation, incomplete tool-use turns, and replay validity before deriving a terminal liveness state.

Only lifecycle termination points to agent.wait in the figure. Treating assistant text as completion can release resources before a tool executes. Treating the last tool result as completion can omit the model's final explanation. Output visibility and run termination need separate protocols.

9. State has owners; “context” is not one bucket

State ownership in an OpenClaw turn: FinalizedMsgContext links through sessionKey to durable SessionEntry and openclaw-agent.sqlite, runEmbeddedAgentInternal owns the temporary runtime view, and ReplyPayload carries outbound content

By this point it is tempting to call everything “context.” The source exposes several distinct surfaces. FinalizedMsgContext carries normalized inbound facts. SessionEntry and transcript data live in durable session storage. The embedded agent constructs a temporary runtime view for this attempt. ReplyPayload describes channel-agnostic outbound content.

ReplyPayload can hold text, fallback text, media, attachments, presentation, and delivery preferences. It does not own the session and should not become the only recovery record. SessionEntry, in contrast, carries durable identity and recovery-related state such as session id, update time, restart recovery fields, and plugin extensions.

This yields a practical diagnostic order:

  1. Wrong inbound content: inspect normalized FinalizedMsgContext.
  2. Mixed history or reordering: inspect sessionKey, the session lane, and durable session/transcript state.
  3. A missing instruction in the model view: inspect runtime assembly and compaction.
  4. A correct model answer that never reached the channel: inspect ReplyPayload, delivery adaptation, and audit.

These failures look similar from a chat window, but they belong to different owners. Prompt changes cannot repair routing, and transcript rewrites cannot repair provider delivery.

10. Failure is a side-effect decision, not one catch block

Return to the test-fixing task. The tool has edited a file, then the model provider times out. Replaying from the original message may edit the file twice. Declaring a clean failure leaves a real side effect unowned. OpenClaw tracks replay validity, delivery evidence, source claims, lifecycle generations, and terminal states at different layers to answer whether the original input remains safe to execute.

  • Ingest/drop/handled: no agent began, so this is not a model failure.
  • Command or policy short circuit: dispatch already has a complete result; no embedded run exists.
  • Model or tool error: failover or abandonment depends on visible output and possible side effects.
  • Interrupted tool-use turn: earlier assistant text does not make an unfinished tool chain a successful end.
  • Duplicate recovery source: a durable claim can justify retiring or skipping work instead of re-executing it.

A reliable agent runtime does not merely “have retries.” It can say who has evidence that a retry is safe. Process-local Promise rejection is not enough once external effects may have happened.

11. Why the next seven chapters follow this order

Eight-part OpenClaw source-reading order: main path, Gateway, routing and sessions, context and memory, capability assembly, security boundary, multi-agent, and always-on recovery

This chapter established coordinates rather than exhausting every subsystem. The remaining ownership breaks become seven focused readings:

  1. Gateway: why it is a control plane rather than merely a WebSocket server.
  2. Routing and sessions: how bindings, peers, threads, sessionKey, queues, and transcripts define ownership.
  3. Context and memory: how workspace, bootstrap, system prompt, history, compaction, and memory stay separate.
  4. Capability assembly: where tools, skills, plugins, and hooks enter the runtime.
  5. Security boundary: how tool policy, sandbox, elevated execution, approvals, and channel identity constrain side effects.
  6. Multi-agent: how session tools, subagents, and ACP create new run owners and return results.
  7. Always-on recovery: how heartbeat, cron, durable tasks, and restart recovery extend a turn beyond one process lifetime.

The order moves from “how one run completes” to “how a long-lived system stays coherent.” Starting with cron or subagents would make a new session look like an ordinary function call. Missing the event and tool boundaries would make it impossible to decide what restart recovery should actually recover.

12. Five rules that travel beyond OpenClaw

  1. Normalize first, apply shared admission second.Channel plugins own provider differences; the core owns turn lifecycle.
  2. Establish state identity before selecting execution resources.Session and queue ownership usually constrain correctness earlier than provider/model choice.
  3. The model proposes; the runtime acts.Tool calls, side effects, and tool results require explicit seams.
  4. Content events are not terminal events.Assistant, tool, and lifecycle streams serve presentation, execution observation, and completion.
  5. Replay safety follows evidence of side effects.Once actions may have happened, use durable claims and terminal evidence instead of unconditional retries.

With those rules in hand, part two can pull the camera back from one turn to the Gateway. “Control plane” will then describe concrete protocol ownership—who may observe, configure, start, wait for, and recover these runs—rather than an architecture label pasted onto a server.

Source and documentation index