How Echo works

What Echo is, how it's built, and what it's doing right now.

What Echo is. Echo is a developer assistant for Resonate — retrieval-augmented answers grounded in the docs, the SDKs, and a corpus of real examples. You can reach it five ways: the web app, the API, the CLI, Discord, and MCP. For hard questions there's deep research, available on every surface: Echo breaks the question into sub-queries, retrieves them in parallel, and writes a single cited answer from the pooled evidence.

How Echo is built. Echo runs as two tiers. The query path is synchronous: a request embeds the question, runs a hybrid vector and full-text search over the corpus, reranks the hits, and asks Claude to write the answer — all on a single request, start to finish in a few seconds, most of that the model writing. Deep research takes the second tier instead: the question becomes a durable workflow on Resonate, which gates it against a curated index of the product space, plans it into sub-queries, retrieves every sub-query in parallel, and synthesizes one answer from everything that came back. That same tier maintains the corpus both paths read: nightly crawls of the docs, SDKs, and examples; a weekly pull from Discord; a daily examples-validation pass; and the briefing that produces this page. Both tiers share one Postgres database that holds the corpus as vector embeddings and a full-text index.

Echo's surfaces (web app, API, CLI, Discord, MCP) feed two paths. The default is a synchronous query path that embeds, searches, reranks, and generates an answer with Claude. Deep research requests go to the tier of durable workflows on Resonate instead, which also keeps the corpus fresh. Both tiers share one Postgres corpus (vector + full-text) and the same external model services (Cohere for embeddings and reranking, Claude for generation).SurfacesWeb app · API · CLI · Discord · MCPSynchronous query pathembed → hybrid search → rerank → Claudeanswered live on the request threadDurable workflows · Resonatedeep research · per-question fan-outcorpus refresh · examples validationteam briefing · transparency rollupcrash-safe · auto-retryPostgres corpusvector embeddings + full-text indexModel servicesCohere embeddings + rerankClaude generation + intent

Running the background work on a durable execution engine has a useful side effect: the Resonate server records the state and timing of every step as it goes. The pipeline numbers below are read straight from those records — and because each step is durable, a crashed run replays and finishes instead of failing.

Total queries
644
+7 last 7d
Unique visitors
137
lifetime
Latency 7d
5.5s
p95 12.3s
Helpful 30d
👍 of rated answers

Pipeline health

Each durable pipeline is a Resonate workflow, and the call graph under each tile is the live shape of its last run — the same picture resonate tree prints for a running workflow. The curated source beside each graph is the workflow that drew it: every ctx.run step is one node. Green nodes succeeded, red failed, amber is still in flight. Because the run is durable, a partial run retries on its own instead of counting against the success rate.

succeededin flightfailedno recent run

Scheduled workflows

Docs corpus refresh
Nightly · durable workflow
Healthy
Success 7d
100%
p50 duration
123.9s
Last run
~24h ago
Call graph nodes: runIngest, crawlCorpus, docs · SDKs · examples · Discord, processStaged. Steps flow: runIngest → crawlCorpus; crawlCorpus → docs · SDKs · examples · Discord; docs · SDKs · examples · Discord → processStaged.ctx.runrunIngestcrawlCorpusdocs · SDKs · examples · DiscordprocessStaged
Nightly crawl → stage → chunk/embed/index. crawlCorpus fans out across every source; Discord forum threads fold in on a weekly cadence.
src/ingest/workflow.tstypescript
// echo-ingest · nightly
function* runIngest(ctx: Context) {
  const runId = new Date(yield* ctx.date.now())
    .toISOString().slice(0, 10);

  // crawl every source, then chunk + embed + index
  const staged = yield* ctx.run(crawlCorpus, runId);
  return yield* ctx.run(processStaged, staged, runId);
}

function* crawlCorpus(ctx: Context, runId: string) {
  // one durable child per source —
  // docs · SDKs · examples · Discord, and ~20 more
  const docs    = yield* ctx.beginRun(crawlAndStageDocs, runId);
  const sdks    = yield* ctx.beginRun(crawlAndStageTsSdk, runId);
  const skills  = yield* ctx.beginRun(crawlAndStageSkills, runId);
  const discord = yield* ctx.beginRun(crawlAndStageDiscordThreads, runId);

  return [...(yield* docs), ...(yield* sdks),
          ...(yield* skills), ...(yield* discord)];
}
Examples validation
Daily · durable workflow
Healthy
Success 7d
100%
p50 duration
794ms
Last run
~24h ago
Call graph nodes: runExamplesHealthBrief, persistRunStep, fetchYesterdayStep, fetchTwoDaysAgoStep, postParentStep, fileNewIssuesStep. Steps flow: runExamplesHealthBrief → persistRunStep; persistRunStep → fetchYesterdayStep; fetchYesterdayStep → fetchTwoDaysAgoStep; fetchTwoDaysAgoStep → postParentStep; postParentStep → fileNewIssuesStep.ctx.runctx.runctx.runctx.runctx.runrunExamplesHealthBriefpersistRunStepfetchYesterdayStepfetchTwoDaysAgoSteppostParentStepfileNewIssuesStep
Ingests the daily examples-ci report, diffs against prior runs, files issues for two-strike failures.
src/examples-health/workflow.tstypescript
// echo-examples-health · ingests the daily examples-ci report
function* runExamplesHealthBrief(ctx: Context, report: ExamplesCiReport) {
  yield* ctx.run(persistRunStep, report);

  // diff today against the last two days
  const yesterday  = yield* ctx.run(fetchYesterdayStep, report.run_date);
  const twoDaysAgo = yield* ctx.run(fetchTwoDaysAgoStep, report.run_date);
  const diff = computeDiff(report, yesterday, twoDaysAgo);

  yield* ctx.run(postParentStep, report, diff);

  // two-strike failures get a GitHub issue
  yield* ctx.run(fileNewIssuesStep, diff.new_failing, report.run_date);
}
Daily team briefing
Daily · durable workflow
Healthy
Success 7d
100%
p50 duration
2.3s
Last run
~24h ago
Call graph nodes: runBrief, briefFetchWindow, briefUpdateProfiles, briefGenerateCandidates, briefQualityGate, briefPost. Steps flow: runBrief → briefFetchWindow; briefFetchWindow → briefUpdateProfiles; briefUpdateProfiles → briefGenerateCandidates; briefGenerateCandidates → briefQualityGate; briefQualityGate → briefPost.ctx.runctx.runctx.runctx.runctx.runrunBriefbriefFetchWindow809msbriefUpdateProfiles25msbriefGenerateCandidates38msbriefQualityGate23msbriefPost29ms
Each step is a durable ctx.run. After a crash the workflow replays from the top, completed steps return instantly from their recorded results, and only the unfinished step runs again.
src/proactive/brief.tstypescript
// echo-proactive · posts the daily community brief
function* runBrief(ctx: Context) {
  const now = yield* ctx.date.now();
  const briefDate = new Date(now).toISOString().slice(0, 10);
  const since = new Date(now - 24 * 3600_000).toISOString();

  const window     = yield* ctx.run(briefFetchWindow, briefDate, since);
  const enriched   = yield* ctx.run(briefUpdateProfiles, window);
  const candidates = yield* ctx.run(briefGenerateCandidates, enriched);
  const scored     = yield* ctx.run(briefQualityGate, briefDate, candidates);
  yield* ctx.run(briefPost, briefDate, scored);

  return { posted: scored.length };
}

Deep research

Deep research runs on the same Resonate engine as the pipelines above, but per question rather than on a schedule. The request gets a research ID back immediately and the run carries on server-side: a gate checks the question against the curated product-space index, the workflow fans out into sub-queries retrieved in parallel, and a single synthesis step writes the cited answer. Because the run is durable, disconnecting doesn't cancel it — any surface can come back with the same ID and the finished answer is waiting.

Call graph nodes: runResearch, initRunStep, gateAndPlanStep, fanOutSearchStep, up to 6 sub-queries · in parallel, synthStep, finalizeStep. Steps flow: runResearch → initRunStep; initRunStep → gateAndPlanStep; gateAndPlanStep → fanOutSearchStep; fanOutSearchStep → up to 6 sub-queries · in parallel; up to 6 sub-queries · in parallel → synthStep; synthStep → finalizeStep.ctx.runctx.runctx.runctx.runrunResearchinitRunStepgateAndPlanStepfanOutSearchStepup to 6 sub-queries · in parallelsynthStepfinalizeStep
One durable run per question: gate against the product-space index, plan into sub-queries, retrieve them all in parallel, synthesize one cited answer. Each run belongs to one question, so there's no last-run status to overlay.
src/research/workflow.tstypescript
// echo-research · one durable run per question
function* runResearch(ctx: Context, input: ResearchInput) {
  yield* ctx.run(initRunStep, ctx.id, input);

  // gate + plan against the product-space index
  const plan = yield* ctx.run(gateAndPlanStep, ctx.id, input);
  // (out-of-scope questions deflect and finalize here)

  // every sub-query retrieved in parallel
  const pool = yield* ctx.run(fanOutSearchStep, ctx.id,
    plan.sub_queries.slice(0, RESEARCH_MAX_SUBQUERIES),
    input.req.source_filter);

  // one synthesis over the pooled evidence
  const synth = yield* ctx.run(synthStep, ctx.id, input, pool);
  const reason = synth.grounded ? "answered" : "no_evidence";
  yield* ctx.run(finalizeStep, ctx.id, input, synth,
    reason, plan.sub_queries.length, synth.docCount);

  return { research_id: ctx.id, answer: synth.answer,
           sources: synth.sources, termination_reason: reason };
}

Synchronous query path

With deep research off, the query surfaces answer on the request thread — no durable run, one shared path.

Call graph nodes: web · API · CLI, rate-limit, embed, hybrid search, rerank, LLM, response. Steps flow: web · API · CLI → rate-limit; rate-limit → embed; embed → hybrid search; hybrid search → rerank; rerank → LLM; LLM → response.web · API · CLIrate-limitembedhybrid searchrerankLLMresponse
The default path: all three surfaces converge on one synchronous pipeline, answered live on the request thread — no background work or queue.
Interactive query (API)
synchronous
No recent data
Answered 7d
p50 latency
Queries 7d
0
Echo web app
synchronous
Healthy
Answered 7d
100%
p50 latency
5.5s
Queries 7d
7
CLI query
synchronous
No recent data
Answered 7d
p50 latency
Queries 7d
0

Daily query volume

By surface, last 90 days.

API: 151 queries over 90dWeb app: 108 queries over 90dCLI: 0 queries over 90dDiscord: 1 queries over 90dMCP: 0 queries over 90d
APIWeb appCLIDiscordMCP

What people ask about

A lightweight classifier sorts each question into one of sixteen topics. This page shows only those bucket counts over the last 30 days — never the question text. Arrows compare the last 7 days to the 7 before.

Durable promises (concept)
9
Call graphs / agent loop (concept)
5
Comparison to other tools
5
Other / unclear
5
Rust SDK
3
Local dev / CLI
3
Retries / error handling
3
Building agents on Resonate
3
Python SDK
2
TypeScript SDK
1
Deployment / hosting
1
Webhooks / external coordination
1

Feedback

Last 30 days.

Helpful 👍
0
Not helpful 👎
0
Ratio
Coverage
0.0%
rated

Latency

p50 / p95, last 30 days.

peak 14673ms
p50p95