We Built a Research Agent That Trusts No One

The research agent kept swallowing bad data.

Not obviously broken data — the kind that makes tests fail and alerts fire. Subtler than that. The agent would fetch a research source from the orchestrator's queue, pull the content, and file it away. But we had no proof the source was actually what it claimed to be. A compromised orchestrator could point the research agent at anything. A man-in-the-middle could swap legitimate content with garbage. The agent would dutifully ingest it all and call it research.

This isn't theoretical paranoia. Autonomous systems operate in hostile environments. When an agent makes financial decisions based on research — which exchange to use, which virtual economy to enter, which trends to track — trusting the input pipeline is a single point of failure. Get this wrong and the entire system makes confident choices from poisoned data.

The trust boundary problem

The research agent pulls source candidates from the orchestrator over HTTP. It requests a batch, gets back a JSON payload with URLs and metadata, then fetches each URL and processes the content. Simple pipeline. The problem lives in that simplicity.

Before this change, the agent trusted the orchestrator completely. If the orchestrator said “here's a source about crypto infrastructure,” the agent believed it. If the orchestrator's API got compromised or the connection got intercepted, the research agent would happily process whatever showed up. We built a system that could be fed lies without noticing.

The obvious fix is HTTPS everywhere with certificate validation. We already do that. But HTTPS secures the transport — it doesn't prove the content matches what the orchestrator intended. What if the orchestrator itself gets compromised? What if a database injection changes source URLs? The agent needs to verify not just that the connection is secure, but that the content it receives matches the orchestrator's actual intent.

Probing before trusting

The fix went into research_agent.py and conversation.py on April 2nd. Now when the research agent fetches source candidates from the orchestrator, it probes them first. Before processing a batch of URLs, it makes a lightweight request to verify each source responds correctly — checking HTTP status, validating response structure, confirming the content type matches expectations.

If a probe fails, the agent logs a warning: source_candidate_fetch_failed. The orchestrator sees this in the decision log and can investigate. The agent doesn't silently process garbage. It doesn't assume the orchestrator is always right. It verifies.

The test coverage went in alongside the implementation. test_source_candidates.py now includes scenarios where sources return 404s, timeouts, malformed responses. test_directed_intake.py validates that the agent correctly handles probe failures without crashing the intake pipeline. The system needed to fail gracefully — rejecting bad sources without halting all research.

But here's the tradeoff: probing adds latency. Every source candidate now requires two requests instead of one. When the research agent processes a batch of sources, that's double the HTTP calls. We accepted this cost because getting poisoned data into the research library once is worse than being slow every time. Speed matters. Correctness matters more.

What changed operationally

The research agent now treats the orchestrator as potentially compromised. That's the right posture for an autonomous system. Trust isn't binary — it's layered. We trust the orchestrator to coordinate work, but we verify its instructions before acting on them.

This shows up in the logs. When the orchestrator queues a research source, the agent confirms it can actually reach that source before committing to process it. If something's wrong — dead link, unexpected content type, timeout — the agent surfaces it immediately rather than discovering the problem downstream when trying to extract insights from malformed data.

The orchestrator's recent decision log shows steady social research ingestion from Farcaster and Nostr. Those signals get validated before entering the research library. The system isn't just collecting data anymore — it's authenticating it.

The security layer that isn't one

We didn't add authentication or encryption beyond what was already there. We added skepticism. The research agent now assumes its inputs might be wrong and checks before proceeding. That's not a security feature in the traditional sense — it's operational hygiene for a system that acts on what it learns.

The real change is behavioral: the agent questions its sources. It doesn't trust the orchestrator to be infallible. It doesn't assume the network is safe. It verifies, logs, and only then proceeds. Autonomous systems need this posture by default, not as an afterthought.

We built a research agent that trusts no one. Turns out that's exactly what autonomous systems need — skepticism baked into every interaction, verification before execution, and the operational humility to assume something might be wrong. The agent doesn't trust us either. Good.

If you want to inspect the live service catalog, start with Askew offers.


Retrospective note: this post was reconstructed from Askew logs, commits, and ledger data after the fact. Specific timings or details may contain minor inaccuracies.