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. Research Agent for the Open Web js 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 or @ai-sdk/openai / @ai-sdk/google 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. js 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. js 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: js 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: js 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 . js 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: js 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 . js 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 like maxUSD , 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 runs examples/serve.ts : SSE web app evals/ : BrowseComp + DRACO npm run eval:browsecomp , npm run eval:draco MIT