An autoresearch loop is only as good as its memory of what it tried. Run one experiment overnight and a markdown scratchpad is fine. Run a thousand in parallel and that scratchpad stops being the record. The record is everything the agents actually did: every model call, every tool invocation, every diff, every shell command, every byte over the wire.
The hard part is that the thing producing all of that, the agent harness, changes constantly. A schema you design around this month’s harness is wrong next month. A fixed set of dashboards answers last week’s questions. So we made one choice, and we made it twice: capture low and reconstruct high. Record a raw, generic stream of what happened, then reshape it into whatever the research needs at query time, on an engine flexible enough for questions nobody’s thought to ask yet.
Capture low
You don’t know what the next harness will look like, so the worst thing you can do is model it. Our interceptor wraps a harness as a child process and watches it from the outside: the OpenTelemetry it already emits, its network calls (HTTPS included, through a proxy it mints per run), and its stdio. It makes no assumption about the framework inside. Capture is cooperative: the interceptor hands the harness a proxy and a certificate authority for the run, so a client that pins its own certs won’t be captured. That covers the harnesses people actually run, and it keeps us out of the kernel.
Normalization is generic first. We record where a byte came from, a process, an HTTP body, a span, not which harness produced it. Everything lands as one deliberately narrow event: a timestamp, run-lineage identity, a signal family (llm, net, exec, span, log), a name, a flat bag of scalar attributes, and a content hash for any large body. Six families, scalars only. That narrowness is the point. It’s what survives a harness you’ve never seen.
Here’s a slice of one real run, where the interceptor wrapped a live Claude Code session:
// run 01JZ… (one turn, 57 events total)
{ "signal": "llm", "name": "chat.completion", "host": "api.anthropic.com",
"model": "claude-opus-4-8", "input_tokens": 18432, "output_tokens": 712,
"body": "blake3:9f2c1a…" }
{ "signal": "net", "name": "http.request", "host": "mcp.internal",
"method": "POST", "target": "/tools/search", "status": 200,
"body": "blake3:4b7e02…" }
{ "signal": "exec", "name": "process.stdout", "bytes": 2048 }
That whole turn was 57 events: thirteen model calls to api.anthropic.com, the harness’s MCP tool traffic, its own telemetry, and its stdio. Every event is stamped with the run and its place in the lineage tree. Fork a run and the child is a new run hung beneath its parent, so a branch is a run, not a position inside one. Every large body, prompts and completions included, is stored once in a content-addressed (blake3) blob store and referenced by hash. The run shipped to our gateway and came back queryable, tenant-isolated, end to end.
Reconstruct high
A narrow raw record is only useful if you can turn it back into something rich. The trick is to keep the raw stream as the single source of truth and treat every richer shape as a view derived from it, and rebuildable from it.
From the same events you can project:
- a span tree, for a human debugging one run;
- a trajectory of observation, action, and result, for understanding or comparing what an agent did;
- a training export, the same trajectory reshaped into SFT or RL samples.
Because the views are derived, we never had to guess at capture time whether you wanted to debug, compare, or train. When a new harness shape arrives, you add a normalizer and a view, and the history reshapes to match. You decide the goal late and change it freely.
All of it sits behind one SQL surface. We built the engine on Apache DataFusion: events land as sorted Parquet on object storage with a Postgres catalog for pruning, and queries run as plain SQL over Arrow. We built on a general-purpose query engine because autoresearch has no fixed set of questions.
Questions shaped like research
The query a research loop asks is rarely “show me this run.” It’s “how did these two branches differ after they forked?” A branch is a run, and lineage is a materialized path of run ids (01JZ…A.01JZ…B), so an ancestor is a prefix and a whole subtree is an anchored prefix scan, not a walk of the tree. A branch-diff compares two runs directly:
-- what did branch B do that sibling A did not?
SELECT signal, name, count(*) AS n
FROM events
WHERE run_id = '01JZ…B'
AND event_id NOT IN (
SELECT event_id FROM events
WHERE run_id = '01JZ…A'
)
GROUP BY signal, name
ORDER BY n DESC;
Three more retrieval patterns make a research loop fast:
- Annotations. Some judgments are expensive: an eval, an LLM-as-judge pass, a human label. Compute one once and write it back as a first-class annotation that travels with the run and its lineage. After that, “find the experiments where the fix actually held” is a predicate, not a re-evaluation.
- Free-form SQL, isolated by default. The surface is SQL over your own events, not a fixed set of endpoints. Tenant isolation is forced server-side from a trusted identity, never read from the request body, so a cross-tenant read isn’t expressible.
- Semantic and full-text search, coming soon. Annotations cover the judgments you knew to compute. The long tail, “find experiments like this one” over prompts, completions, and diffs, wants vector and text search. That’s the next thing we’re adding, behind the same SQL surface.
Couple state to observability
We’re aggressive about decoupling everything else, and just as deliberate about coupling one thing: what the agent did and what the machine looked like while it did it.
Every event and every sandbox snapshot carries the same run-lineage key. So you can join the trace to the state, answer “what was on disk when the agent ran that command,” and replay a run against its snapshots to reconstruct the machine at any point in the trace. Telemetry bodies and sandbox artifacts are both content-addressed, so a prompt and a filesystem are addressed and deduplicated the same way. Lineage falls out of one mechanism instead of two systems with two clocks.
The industry is converging on OpenTelemetry’s GenAI conventions for agent telemetry, and we ingest them on the capture side. We go further on both ends. We capture the whole network and process stream, not just the spans a harness chose to emit, so the raw record is complete. And we put a query engine on top of it, so a research loop’s complex, open-ended questions get answered efficiently.
We’re building the infrastructure to take autoresearch from a single machine to something you run at scale. Capture low, reconstruct high, on an engine that bends to the research instead of the other way around. If you want to work with us, email us at founders@hiloop.ai.
