The Runtime's Parts: State, Memory, Tools, and the Model

With OpenManus's control loop in view, the next problem is ownership: which layer decides what happens next, which layer stores what happened, and which layer is allowed to affect the outside world? This chapter tears the runtime down by responsibility rather than by file list. The goal is practical architectural judgment: when a new capability is added, you should be able to tell which boundary it crosses and which guarantees the core does—or does not—provide.

Time belongs to the agent; context belongs to memory

The outermost owner is the agent lifecycle. BaseAgent holds the current state, step count, maximum number of steps, prompts, model reference, and memory reference. It is the layer that decides whether another iteration may begin. That makes it the owner of time in the runtime: it does not decide the content of every action, but it controls how many opportunities the model receives to propose one.

This distinction is easy to miss because the model appears to drive the interaction. In practice, the model cannot call itself again. The lifecycle invokes step, waits for the result, checks whether the state is finished, and decides whether to repeat. A model can request termination through a special tool, but the runtime interprets that request and changes state. A maximum-step condition can stop the run even if the model would have continued. An exception can move the protected state toward error handling. The agent is therefore the clock and the boundary around repetition.

ReActAgent gives one iteration a smaller division of labor. Its step calls think, and, if the result says an action is needed, calls act. It does not own a second memory system or a second lifecycle. It supplies a shape for the work performed within one tick of the outer loop. ToolCallAgent then fills that shape with an LLM request and tool execution. The inheritance is not merely an object-oriented convenience: it preserves one lifecycle while allowing the decision/action mechanism to be reused by several agents.

Memory owns a different kind of continuity. It stores typed messages rather than deciding whether the run should continue. A user request, a system instruction, an assistant response, and a tool observation are not interchangeable strings. Their roles tell the LLM wrapper how to format them and tell the runtime how to associate a tool result with the call that produced it. Memory can trim old messages when it reaches its limit, but it does not summarize them or decide which fact is most important. That is a context boundary with a real consequence: the lifecycle may still be running while the model's access to early context has narrowed.

The difference between state and memory gives a useful diagnostic rule. If an agent stops too soon or runs too long, inspect lifecycle state and stop conditions. If it forgets an earlier instruction or loses the connection between a request and a result, inspect message retention and context formatting. A fix in one layer cannot automatically repair the other. Increasing the step limit will not restore an evicted message; keeping more messages will not prevent a model from repeatedly choosing an ineffective action.

The model wrapper owns yet another boundary. The LLM class turns the runtime's messages into provider requests, selects a configured client path, adds tool definitions when a tool request is made, and tracks estimated token use. It can support ordinary text requests, tool-aware requests, and images when the configured model is recognized as multimodal. Provider details—model name, endpoint, API type, key, temperature, and token settings—are held in configuration rather than scattered across each agent.

That wrapper is valuable because the rest of the agent can ask for a common operation instead of knowing every provider's request format. It is also where the model's apparent continuity meets physical constraints. Tool descriptions consume input context. Long observations consume more. Images have their own estimates. An optional cumulative input budget can prevent another request before it is sent. The wrapper may retry broad classes of request failures with backoff, which can improve resilience but also adds latency and does not guarantee that every error is worth retrying. The common interface hides provider differences; it does not remove them.

The model layer therefore owns translation and budgeting, not task truth. It can determine that a request cannot fit within the configured input budget, but it cannot determine whether a file was edited correctly. It can convert an image-bearing message for a supported model, but it cannot guarantee visual interpretation. It can return a structured tool call, but the lifecycle still has to validate and execute it. Keeping these ownership lines clear prevents a common category error: treating a provider adapter as though it were an outcome verifier.

The core relationships look like this:

classDiagram class BaseAgent class ReActAgent class ToolCallAgent class Manus class SWEAgent class DataAnalysis class MCPAgent class LLM class Memory class Message class ToolCollection class BaseTool BaseAgent <|-- ReActAgent ReActAgent <|-- ToolCallAgent ToolCallAgent <|-- Manus ToolCallAgent <|-- SWEAgent ToolCallAgent <|-- DataAnalysis ToolCallAgent <|-- MCPAgent BaseAgent --> Memory : owns Memory --> Message : stores ToolCallAgent --> LLM : asks ToolCallAgent --> ToolCollection : dispatches ToolCollection --> BaseTool : indexes

The diagram is intentionally about responsibility, not a complete class reference. It shows a shared path from lifecycle to tool-calling agents, a separate message store, a model adapter, and a collection of capabilities. Manus, software-oriented work, data analysis, and MCP interaction can reuse the same call machinery while changing prompts, limits, and available tools. The behavior a reader experiences is an emergent result of these relationships, not a property of one class in isolation.

A tool contract makes extension easy—and governance incomplete

The smallest capability boundary is BaseTool. A tool has a name, a description, a parameter schema, and an asynchronous execution method. It can format successful or failed results in a common ToolResult shape and export metadata in a function-calling format that the LLM wrapper can send to a model. This is enough to make a new operation legible to the model and callable by the runtime without changing the outer lifecycle.

ToolCollection is the registry and dispatcher around that contract. It maintains a name-to-tool map, converts the tools to model parameters, looks up a requested name, and executes the selected object. It catches the tool-specific error type and turns it into a failure result. When new tools are added, it skips a name that already exists rather than replacing the earlier entry. The collection therefore enforces dispatch correctness: the requested name must be known, the operation must be called through the expected interface, and an ordinary tool error must become an observation rather than crash the whole architecture by default.

Those checks are useful but narrower than policy. The collection does not decide whether a path is appropriate, whether a shell command is authorized, whether a remote server should receive a secret, or whether a successful result proves the user's goal. Those questions belong to a tool implementation, a deployment wrapper, or a human approval process if the application supplies one. The generic interface makes capabilities composable precisely because it does not contain the full meaning of every side effect.

This creates a productive separation and a hidden coupling at the same time. The separation lets OpenManus add a browser tool, a file editor, or an MCP proxy without rewriting how the model is asked for a decision. The hidden coupling is that the model's decision quality depends on the tool's description and schema, while the tool's real safety depends on context outside the schema. A vague description can make a perfectly implemented tool hard to use. A precise description can make a powerful tool easier to call without making the call appropriate.

The same principle applies to remote tools. An MCP client can wrap a server-side operation as a local-looking BaseTool proxy. From the model's point of view, the proxy can be listed and called like other tools. From the deployment's point of view, the call crosses a process or network boundary, may receive server instructions, and depends on the remote session's lifecycle. Reusing the tool contract reduces integration work; it does not make local and remote trust conditions identical.

Specialized agents use composition rather than separate runtimes. A software-oriented agent can reuse ToolCallAgent while exposing shell, editing, and termination operations. A data-analysis agent can reuse the same loop with Python and visualization tools. An MCP agent can place a remote tool collection in the available-tools slot and add refresh behavior. The shared core gives these agents a common lifecycle, common message shapes, common error handling, and common termination semantics. Their differences live in prompts, tool sets, model configuration, and initialization.

This reuse has an important consequence for diagnosis. If a specialized agent fails because it has the wrong tool, changing the shared loop may be unnecessary. If every specialization mishandles context limits or cleanup, the shared layers are the likely place to inspect. If a remote tool behaves differently from a local one, the difference may lie in session state or result translation rather than in the agent's reasoning step. Architecture becomes useful when it tells you where not to look.

The extension points also explain why OpenManus can grow faster than a monolithic task program. A new tool supplies metadata and execution; a new agent selects a tool set and prompt; a new flow can coordinate agents; an MCP server can supply capabilities outside the repository. But a framework that makes addition easy also makes it easy to add an unexamined boundary. The core does not show a central trust classification for local tools, remote tools, or injected instructions. It does not require every side effect to have confirmation, nor does the planning state prove an outcome. Those absences are not proof that a downstream application cannot add such controls. They do show that the generic contracts should not be mistaken for a complete governance model.

A helpful way to read an OpenManus extension is to ask four separate questions. What does the model know about the capability? What state does the capability own? What observation comes back, and how much of it survives into context? What policy or verification exists beyond the call interface? The first three are partly answered by the repository's tool, memory, and LLM layers. The fourth is often an integration responsibility.

Imagine adding a tool that retrieves a page and extracts a short passage. The implementation must give the model a name and a description, specify the required URL, and return a result in the common tool format. The collection can then expose it alongside other tools without changing the agent's loop. The model can choose it, the runtime can parse its arguments, and the result can return to memory. From the core's perspective, this is a clean extension.

But the extension also crosses several less visible boundaries. The description becomes part of the LLM request and consumes context. The URL may point to a service with its own authentication, rate limits, or instructions. The returned passage may be truncated or stripped of important page context. If the tool changes the shape of its result, a prompt or downstream agent behavior may change even though the outer lifecycle is unchanged. A small interface can therefore carry large operational assumptions.

The same reasoning applies to an MCP proxy, but with an added session boundary. The proxy can look like an ordinary tool to the model while the client maintains a connection, maps a local name to a server-side name, and converts remote text or images into a local result. The remote server may expose instructions that become system messages in the agent's memory. Disconnecting the session removes the tools, yet messages already written into memory may still describe what was available earlier. The lifecycle of capability and the lifecycle of conversation are related, but not identical.

Specialized agents make this architecture useful in practice. A software-oriented agent can choose a different tool collection without inventing a new way to store messages or count steps. A data-analysis agent can add Python and visualization operations while preserving the same model-call and observation path. An MCP agent can add tool refresh behavior because its remote capability set can change. The shared core means improvements to message handling or termination can benefit several agents, but it also means a regression in a shared layer can affect several seemingly different applications.

This is where maintenance becomes part of architecture. Adding a tool may require updating its schema, its implementation, its cleanup behavior, and the environment in which it runs. Changing a model may alter image support or token estimates. Moving a capability from a local implementation to an MCP server changes startup, naming, credentials, and failure behavior even if the model-facing description remains similar. Replacing a specialized agent's prompt can change which tool it selects without changing any Python interface. The reusable core lowers the cost of composing a system; it does not eliminate the cost of understanding the composition.

The final policy question is especially important for tools with side effects. Suppose a file-editing tool returns “success.” The collection can confirm that a tool with that name ran and returned a result. It cannot, by itself, confirm that the edited file is the right file, that the change was authorized, or that the user's larger objective has been achieved. Those checks might belong in the tool, in a verification step, in an approval layer, or in the surrounding application. OpenManus's generic interfaces leave room for all of these designs, but they do not select one for the integrator.

That last question is why a component teardown must end by reconstructing the operating model. OpenManus is not simply a stack of reusable classes. It is a set of owners connected by feedback: the lifecycle invokes the model, the model selects a capability, the collection dispatches it, the tool returns an observation, memory preserves some context, and the lifecycle decides whether to repeat. The next step is to test whether that model still holds when the pieces are considered together—especially when a new capability brings its own state, instructions, limits, and failure modes.

References