In Software After AI, I suggested that more software might become material an AI can use and combine. That leaves a practical question: what makes those pieces work together long enough to finish a task?
Consider a request: “Find out how this application's search works.” Someone has to identify the application, inspect its contents, follow references, decide which clues matter, and keep enough evidence to explain the answer. A model can propose those steps. Each proposal still has to become an operation on a particular file or running process.
REA, an open-source reverse-engineering toolkit, makes this boundary interesting to examine. It exposes analysis capabilities through a CLI and an MCP server, with a Skill that guides an agent's use of them. I wanted to see what happens between asking the model a question and getting back something we can inspect.
The walkthrough below is an illustrative request traced against the source, rather than a report of a completed investigation. The implementation details refer to REA at commit 3d2846f.
Following the search
Suppose the application is packaged JavaScript. REA has a static analysis path that can inspect a directory or ASAR archive without executing the application. Its documented workflow inventories artifacts, parses source, and constructs relationships that can help an investigator locate entry points and follow dependencies.
The model receives the request and the available tool descriptions. It might propose analyze_javascript_application. The surrounding agent runtime dispatches that call to REA. REA performs the analysis and returns a result; the runtime puts that result back into the model's context. Only then can the model choose a useful next step.
Figure 1. An illustrative tool round trip. A real investigation can repeat this cycle many times, with different operations and providers.
REA's MCP entry point loads configuration, creates an analysis session, opens an initial target when configured, and starts the transport. It also arranges cleanup. The model conversation runs outside this entry point. REA receives operations with arguments, and owns the work needed to give those operations a defined meaning.
For the search investigation, suppose the model asks for a summary of the JavaScript analysis. REA retains the full analysis in the session and returns a smaller view with a reference to it. The model can then ask to see a page of modules:
{
"source": {
"kind": "retained-evidence",
"evidence_id": "<parent_evidence_id from the analysis response>"
},
"view": {
"kind": "page", "collection": "modules",
"offset": 0, "limit": 16
}
}
These are illustrative arguments to inspect_analysis_view. The parent_evidence_id comes from the summary response. Paging changes how much is sent back to the model, without running the analysis again or changing what it examined.
Suppose that page contains a module called search.js. The name gives us somewhere to look. REA's feature-tracing workflow can follow recorded relationships from a selected module. The next tool call could use the same retained analysis:
{
"name": "trace_application_feature",
"arguments": {
"application": {
"kind": "retained-evidence",
"evidence_id": "<same parent_evidence_id>"
},
"seed": { "kind": "module", "value": "search.js", "match": "exact" }
}
}
Here search.js is an assumed module name, not a finding from a tested application. In an investigation, the model would use the exact module value returned by the analysis. The reference keeps this request attached to the same evidence as the previous one.
The trace might lead toward a local index, or toward code that sends an HTTP request. Those clues would lead to different follow-up work. Finding a request in the source, for example, would give us a reason to inspect how it is constructed and how the response is used. It would not yet tell us how the remote service searches its data.
Even a correctly recorded trace can leave the explanation wrong. Static analysis may miss a dynamic call, and a path in the source need not be a path the application actually takes. REA's documentation keeps these limitations explicit. To claim that a search behaves a particular way, we would need further evidence, perhaps from observing the application with known inputs.
Keeping track of where a result came from gives us something to return to when we question the explanation.
What has to survive the next call
The agent runtime keeps the conversation: the request, proposed calls, returned results, and what the model needs for its next decision. REA keeps domain state: the target, provider binding, retained analysis, and evidence identities. If the conversation remembers that we were investigating search but loses access to the analysis, it cannot continue from a module name alone. It needs the records that gave that name its meaning.
The machinery around the model is usually called a harness. It keeps the task moving: requesting a response, executing proposed tools, recording results, and continuing the interaction. Particular harnesses add their own permission, interruption, persistence, and recovery mechanisms.
OpenAI's description of Codex as a platform puts conversation state, the agent loop, tool interactions, and execution boundaries in this reusable layer. An application integrating it still supplies its own context, tools, and business rules. Reusing the loop does not settle what a domain operation means.
DeepSeek Harness's architecture makes another version of this separation visible. Its agent loop prepares model input and tool schemas, requests a response, executes tools, records results, and continues. Sessions use an append-only log from which model messages can be derived. Model adapters, tools, and the loop itself are replaceable plugins. This is a different implementation of machinery a domain application would otherwise need to assemble.
Figure 2. A simplified responsibility map. Codex and DeepSeek Harness are alternative examples of the outer runtime; this is not a claim that REA requires both or ships these integrations.
REA's Skill supplies instructions about how to use the tools and interpret their results. The executable software opens targets, runs analysis, and keeps the evidence. Both contribute to the investigation, but instructions alone cannot do the work of those programs.
There are similar responsibilities on the native-analysis side. REA can use Hopper, Ghidra, or IDA. Its provider contract requires a choice when several providers are available and applicable; a selected provider is not silently replaced after a failure. An investigation should not drift between engines without accounting for the change in its evidence.
Continuing after an interruption brings another problem. REA's MCP contracts say that retained evidence references belong to the current connection. A fresh connection has its own records. Calling close_binary clears the retained records, even when no binary is open.
So the search investigation needs more than a saved conversation if it is to continue later. The evidence can be exported before closing, then imported into a new connection. Another option is to supply complete evidence inline, or run the analysis again. Keeping a result available for the next call and saving it for another session are separate operations.
The runtime also needs to distinguish giving up waiting from the analysis actually stopping. Otherwise it may start more work while the earlier operation is still running. A model can decide that it wants to try again; software has to establish what is still in progress.
Deciding the next step
A developer could write a fixed pipeline for the search investigation: inventory the package, find likely search modules, inspect references, and produce a report. The pipeline would need rules for deciding which modules qualify and what to do when the expected clues are absent.
With an agent, some of those choices can wait until the results arrive. The model can follow the HTTP request because it appeared in this application, even if nobody wrote a search-specific rule telling it to take that branch. The program that traces the references still needs a precise request.
A rough description of the outer loop is:
result = execute(proposed_operation)
record(result)
next_operation = ask_model(task, history, available_tools)
This is only part of the loop. It leaves out permission checks, failures, and the decision to stop. The change I am interested in is where the next operation comes from. A developer can write the selection rules in code, or leave some choices to the model after it has seen the results.
Figure 3. A conceptual comparison. Both forms can coexist within one application, and a tool may itself contain a fixed workflow.
When a useful sequence becomes a tool
I can imagine more software being built around this arrangement. A reusable harness would carry the task across model calls. A domain package would supply the operations and manage their records, including what must be saved to use them later. REA's shared CLI and MCP workflows suggest how one capability can serve both a scripted process and an agent. A graphical interface could use the same application layer, provided it respects the same contracts.
The search example suggests another possibility. Suppose an agent repeatedly inventories a package, identifies likely search modules, traces their references, and prepares a set of observations for review. If that sequence works across enough cases, a developer could make it a single operation. It would need tests for the cases where the expected module is absent or the trace is incomplete. Its result would still need to say what was observed and what remains unknown.
The agent could then choose that operation when it fits the task, without reconstructing every intermediate step. A sequence discovered while working on one application might become a tool used to inspect many others. Some work would move back into ordinary code as we learned more about it.
How far this organization spreads will depend on the work. Some tasks benefit from interpreting unexpected intermediate results. Others have well-understood sequences where a model adds cost and another source of failure. REA provides a concrete case for examining the boundary, without establishing a universal replacement for existing application architectures.
One detail in REA's snapshot reuse rules brings this back to a smaller question. A cached analysis is eligible for reuse only when the target bytes, operation, parameters, provider, and relevant settings match. If the application has changed, remembering that we already examined search.js is insufficient. We need to know which version we examined and whether the observation still applies.
Find me on StratCraft | GitHub



Top comments (0)