cd /news/ai-agents/atlas-open-source-deep-research-you-… · home topics ai-agents article
[ARTICLE · art-39611] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Atlas – open-source deep research you own

Steel.dev released Atlas, an open-source deep research agent that allows users to own their research data. Atlas decomposes questions, routes sub-tasks to various researchers like Exa and Perplexity, and synthesizes reports, supporting multiple search and fetch providers. The tool aims to give users full control over their research process and data.

read6 min views1 publishedJun 25, 2026
Atlas – open-source deep research you own
Image: source

Research Agent for the Open Web

import { Atlas } from "@steel-dev/atlas";
import { anthropic } from "@ai-sdk/anthropic";

const atlas = new Atlas({ model: anthropic("claude-fable-5") });
const { report } = await atlas.research(
  "What's changing in browser automation for AI agents?",
);

One-off query, no install:

ANTHROPIC_API_KEY=sk-ant-... npx @steel-dev/atlas "How do reasoning models differ from standard LLMs?"
npm install @steel-dev/atlas ai @ai-sdk/anthropic

Search: tavily.search()

/ exa.search()

/ brave.search()

(auto from env keys), or native.search({ model })

to use the model provider's own web search.

Fetch: basic.fetch()

by default; steel.fetch()

for JS-rendered pages when STEEL_API_KEY

is set.

import { Atlas, exa, steel, basic } from "@steel-dev/atlas";

const atlas = new Atlas({
  model,
  search: exa.search(),
  fetch: [basic.fetch(), steel.fetch({ proxy: true })],
});

Register other research agents — including Atlas itself — as a fleet. Atlas decomposes the question, routes each sub-task to the best-fit researcher (query → report

), runs them in isolation, then synthesizes one report. With no researchers

set it stays a single spine run.

import { Atlas, exa, perplexity, parallel } from "@steel-dev/atlas";

const atlas = new Atlas({
  model,
  researchers: {
    exa: exa.agent(), // Exa deep-research (via exa-js)
    perplexity: perplexity.agent(), // Perplexity Sonar
    parallel: parallel.agent(), // parallel.ai task API
  },
});

await atlas.research("Compare X across academic and shopping angles");

Any query → report

worker plugs in via researcher({ description, research })

description

drives routing. atlas.asResearcher(description)

exposes an Atlas instance as a worker, so fan-out can recurse. A worker returns { report, sources }

and may add cost

(USD).

Plug in domain sources with researchTool

: anything via ctx.addSource

flows through the same source store:

import { Atlas, researchTool } from "@steel-dev/atlas";

const atlas = new Atlas({
  model,
  tools: {
    pubmed_search: researchTool({
      description: "Search PubMed.",
      inputSchema: z.object({ query: z.string() }),
      execute: async ({ query }, ctx) => {
        const studies = await pubmed.search(query, { signal: ctx.signal });
        for (const s of studies)
          ctx.addSource({ url: s.url, title: s.title, content: s.abstract });
        return studies.map((s) => `- ${s.title}`).join("\n");
      },
    }),
  },
});

ctx

(ToolContext

) provides addSource({ url, title?, content })

, fetchText(url)

(a guarded fetch returning extracted text or null

), log(message)

, and signal

.

Models per role: the top-level model

is the lead; override the stage models with models.research

and models.write

. Each defaults to the lead model when unset.

Z.ai / GLM: use Z.ai through its OpenAI-compatible endpoint:

import { createOpenAI } from "@ai-sdk/openai";
import { Atlas, tavily } from "@steel-dev/atlas";

const zai = createOpenAI({
  apiKey: process.env.ZAI_API_KEY!,
  baseURL: "https://api.z.ai/api/paas/v4",
});

const atlas = new Atlas({
  model: zai.chat("glm-5.2"),
  search: tavily.search(),
});

The examples also accept --provider zai

with ZAI_API_KEY

/ ATLAS_ZAI_API_KEY

. Configure tavily.search()

, exa.search()

, or brave.search()

for search; Atlas does not map Z.ai's web-search API to an AI SDK native search tool.

Concurrency: concurrency: { models: 4, io: 10 }

or ATLAS_MODEL_CONCURRENCY

/ ATLAS_IO_CONCURRENCY

.

const run = atlas.start(question, { effort: "balanced" });

let preview = "";
for await (const e of run.events()) {
  if (e.type === "report.reset") preview = "";
  if (e.type === "report.delta") preview += e.text;
  if (e.type === "source.fetched") console.error(e.url);
}

const result = await run.result();

report.delta

carries the working draft as it is written and revised; report.reset

precedes each rewrite, so clear your preview buffer on it. report.completed

(and result.report

) is canonical after citation binding. run.finish()

synthesizes from whatever's gathered so far; run.abort()

discards it. Late subscribers get full event history.

Journaled runs replay completed model/search/fetch calls without new provider charges after a crash or deploy:

import { Atlas, fileStore } from "@steel-dev/atlas";

const store = fileStore("./runs");
const atlas = new Atlas({ model, store });
atlas.start(question, { runId: "run_42" });
// …restart…
await new Atlas({ model, store }).resume("run_42");

Replay issues no new provider calls, but it re-charges the run's original budget and token caps as it restores progress — so a resumed run continues within the headroom left when it stopped, and resume()

keeps those original caps (it doesn't take a higher budget). result.stats.costUSD

reports only the new spend, excluding replayed calls.

result.report; // cited markdown
result.note; // short note on how the research was approached
result.citations; // { sourceId, marker } per cited source — marker is its [N] in the report (same contract on single-spine and orchestrated runs)
result.unboundCitations; // source ids cited in the draft that didn't resolve to a fetched source
result.warnings; // non-fatal issues (e.g. a researcher that returned nothing)
result.sources; // sources backing the report (via = fetch method, or the researcher that returned it on a fleet run)
result.stats; // cost, tokens, duration, …
result.trace; // timing/cost trace + bottleneck digest when trace !== "off"

Pass a schema

to get a typed object extracted from the finished report, returned alongside the full result. It runs as a final pass over the report, so it works on any path — single spine run, orchestrated fleet, or an outsourced researcher — and its cost is charged to the same budget and counted in result.stats.costUSD

.

import { z } from "zod";

const r = await atlas.research("Acme's latest annual revenue and CEO?", {
  schema: z.object({ revenue: z.string(), ceo: z.string() }),
});

r.object; // typed: { revenue: string; ceo: string }
r.report; // the cited report it was extracted from

One meter for everything. Pick an effort, or override any cap with budget

.

effort ~budget sources tokens
fast
$0.50 15 200K
balanced
$2.50 40 1M
deep
$10 100 4M
max
$40 250 16M
await atlas.research(question, {
  effort: "deep",
  budget: { maxUSD: 5, maxTokens: 50_000_000 },
});

maxUSD

is a best-effort target, not a hard ceiling. The meter checks between agent turns, so a run stops starting work once spent, but in-flight calls (up to concurrency.models

) can overshoot. Pricing comes from a built-in table that can lag provider changes — pass pricing

to correct a rate when the cap must be accurate.

The real backstops are price-independent — each defaults to the effort row above and is enforced regardless of prices:

budget.maxTokens

— input + output tokens run-wide (cache reads excluded). Checked between turns likemaxUSD

, but never drifts with prices.budget.maxSources

,budget.maxDurationMs

— fetched-source and wall-clock caps.

result.stats

reports budgetExhausted

and tokensExhausted

so you can see which limit bound the run. Leave headroom on maxUSD

, or set a provider-side spend limit, when the cap is truly hard.

result.stats.stopReason

folds those into one value — "completed"

, "finished"

(run.finish()

), or a binding cap ("budget"

, "tokens"

, "timeout"

). When several apply, the most proximate wins.

git clone https://github.com/steel-dev/atlas.git && cd atlas
npm install && npm run dev -- "your question"

examples/cli.ts

: terminal runsexamples/serve.ts

: SSE web appevals/

: BrowseComp + DRACO (npm run eval:browsecomp

,npm run eval:draco

)

MIT

── more in #ai-agents 4 stories · sorted by recency
── more on @steel.dev 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/atlas-open-source-de…] indexed:0 read:6min 2026-06-25 ·