SSE Events

All SSE event types streamed during research — schemas and examples

The /research and /research/upload endpoints return Server-Sent Events (SSE). Each event has a type and data field.

Connection

curl -N -X POST https://api.krawl.sh/research \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-key" \
  -d '{"query": "AI agents", "mode": "deep"}'

Events arrive as:

event: session
data: {"session_id": "uuid"}

event: plan
data: {"status": {...}, "plan": [...]}

event: source
data: {"url": "...", "title": "...", "tool": "webSearch"}

event: report_chunk
data: {"text": "chunk of markdown..."}

event: result
data: {"markdown": "full report...", "sources": [...]}

event: done
data: {}

Keepalive pings are sent every 10 seconds.

Research streams include a sequential id on each event. Reconnect to GET /research/{session_id}/events with the last received Last-Event-ID to replay later buffered events. Replaying events does not restart research or repeat provider calls; durable execution recovery is separate.

Catalog at a glance

Every event Krawl emits, when it fires, and which renderer on the web app consumes it. The renderer column references components under apps/web/src/components/research/ — use it to map SSE → UI when wiring a new client.

EventWhen it firesPrimary rendererConsumed by
sessionFirst frame on stream open—persisted in useSession(); powers steer + follow-ups
memory_recallBefore planning, if prior memories match<MemoryRecallStrip />shows the recalled-from-KB pills
planAfter planning phase (Sonnet 4.6)<PlanCard />top-of-stream plan topic list
perspectivesAfter STORM perspective discovery (Haiku 4.5)<PerspectivesBoard />3–5 perspective chips
node_startedEach new node in the recursion tree<ResearchTree />adds a node to the tree visualisation
queryEach search query that goes out<EventFeed />one row per query in the live feed
thinkingReflection between tool calls (flat/quick mode)<ThinkingBubble />renders the inline thought
sourceEach new source discovered<SourceList />adds the URL to the cited-sources list
contentContent extracted from a source (flat mode)<SourceList />source row gains a content_length chip
x_searchX/Twitter aggregated search frame<XSearchCard />X-only card with tweet count + summary
browse_pagePage fetched via browsePage tool<SourceList />source row marked (browsed)
learningNew extracted learning passes dedup<LearningsList />learning ticker, sorted by confidence
gap_detectedSonnet detects a gap; will trigger recursion<GapsList />gap chip in the right rail
outline_updatedDynamic outline refreshed with new evidence<OutlinePane />report TOC live-updates
node_completedNode finishes (own sources + learnings counted)<ResearchTree />node turns green, shows counts
report_chunkStreaming markdown token frame (many per run)<ReportViewer />typewriter into the main report pane
citation_verifiedA [n] cite is verified against the fetched text<ReportViewer />renders verified badge on the inline citation
chartTime-series data extracted (crypto modes mainly)<ChartRenderer />inline chart in the report
structured_outputOutput extracted per output_schema<StructuredOutputPanel />structured fields panel with citations
review_passMulti-pass review scored the draft<ReviewBanner />review pass + score banner
auditFull audit trail flushed before result<AuditTrail />the /audit/{id} viewer
steerA steer instruction was accepted<SteerPill />toast + steer history entry
documentAn uploaded doc finished processing<UploadedDocs />upload card shows chars_extracted
usageTerminal usage + cost telemetry<UsageFooter />totals + per-model breakdown footer
changes_since_lastDiff vs previous result in the session<ChangesPanel />"what changed" summary card
resultFinal result frame — markdown + sources + learnings<ReportViewer />final report state; saved actions wait for the terminal usage acknowledgement
doneStream complete<EventFeed />flips status to done
errorStream-fatal error<EventFeed />error toast + retry CTA

Event Types

session

Emitted first. Contains the session ID for steering and follow-up research.

{"session_id": "550e8400-e29b-41d4-a716-446655440000"}

plan

The research plan generated by the planning phase.

{
  "status": {"title": "Research plan ready, starting recursive deep research"},
  "plan": [
    {
      "title": "AI Agent Architectures",
      "todos": [
        "Research current AI agent frameworks",
        "Find recent papers on agent architectures"
      ]
    }
  ]
}

perspectives

STORM perspective discovery results. 3–5 perspectives for multi-angle research.

{
  "perspectives": [
    {
      "name": "AI Researcher",
      "description": "Focuses on technical architecture and capability benchmarks",
      "key_questions": ["What architectures dominate?", "How do agents handle tool use?"]
    }
  ]
}

thinking

Agent's internal reasoning between tool calls (flat/quick mode).

{
  "thought": "The search results cover frameworks but not benchmarks. I should search for agent evaluation methods.",
  "nextStep": "Searching for AI agent benchmarks"
}

query

A search query being executed.

{"query": "AI agent frameworks 2025", "tool": "webSearch"}

source

A new source discovered during search.

{
  "url": "https://arxiv.org/abs/2401.00001",
  "title": "A Survey of AI Agents",
  "tool": "webSearch"
}

content

Content extracted from a source (flat mode).

{"url": "https://...", "content": "Extracted text...", "chars": 3000}

X/Twitter search results.

{
  "query": "AI agents",
  "text": "Summary of X discourse...",
  "tweets_count": 12,
  "citations": ["https://x.com/..."]
}

browse_page

Page content fetched via browsePage tool.

{"url": "https://...", "title": "...", "content_length": 5000}

report_chunk

Streaming markdown chunk during synthesis. Arrives many times.

{"text": "## Introduction\n\nAI agents have evolved..."}

result

The final research result. Contains the complete report and all sources.

It includes a logical id, query, mode, created_at, measured duration_seconds, and persisted: false. This identity supports inline evidence-pack export, including ZDR runs. It does not imply that a saved report exists. Only enable saved-report downloads and sharing after the terminal usage.result_id acknowledges successful persistence.

{
  "markdown": "# Full Research Report\n\n...",
  "sources": [
    {"url": "...", "title": "...", "source_type": "exa", "content": "..."}
  ],
  "total_steps": 24,
  "total_learnings": 18,
  "model": "bedrock/us.anthropic.claude-opus-4-7",
  "session_id": "...",
  "learnings": [...],
  "structured": null
}

error

An error occurred during research.

{"message": "Research failed unexpectedly"}

done

Research stream is complete. No more events will be sent.

{}

node_started

A new node in the recursive research tree has started.

{
  "node_id": "...",
  "query": "AI agent frameworks",
  "depth": 3,
  "breadth": 4,
  "parent_id": null
}

node_completed

A research tree node has completed.

{
  "node_id": "...",
  "learnings_count": 5,
  "sources_found": 8,
  "gaps_found": 2
}

learning

A new learning extracted from search results.

{
  "content": "GPT-4 based agents can solve 78% of SWE-bench tasks",
  "source_url": "https://...",
  "confidence": 0.95
}

gap_detected

A knowledge gap identified during research.

{
  "question": "How do agents handle long-horizon planning?",
  "priority": 0.9,
  "suggested_tools": ["webSearch", "academicSearch"]
}

outline_updated

The dynamic report outline has been updated with new evidence.

{
  "sections": [
    {"title": "Agent Architectures", "evidence_count": 5},
    {"title": "Tool Use Patterns", "evidence_count": 3}
  ]
}

citation_verified

A citation has been verified against the fetched source content.

{
  "claim": "GPT-4 achieves 78% on SWE-bench",
  "source_url": "https://...",
  "verified": true,
  "source_excerpt": "Our evaluation shows GPT-4..."
}

audit

The full audit trail for the research session. When present, it precedes result so a disconnect immediately after the report preserves applied controls in the saved audit.

{
  "entries": [
    {
      "timestamp": "2025-01-15T10:30:00Z",
      "action": "search",
      "tool": "webSearch",
      "sources_found": 8
    }
  ]
}

steer

The latest queued steering instruction was consumed at a quick agent turn, recursive query or synthesis/refinement boundary. It applies to that call and subsequent child queries/report scope; it cannot alter a provider request already in flight. Instructions superseded before the boundary are not emitted as applied events. The consumed directive also scopes structured output when output_schema is requested; the original research query remains available as context.

{"instruction": "Focus more on security aspects"}

chart

Data visualization extracted from research findings.

{
  "chart_type": "bar",
  "title": "Market Cap Comparison",
  "data": {...}
}

structured_output

Structured data extracted per the provided output_schema.

{
  "fields": {
    "market_cap": {
      "value": "$400B",
      "confidence": 0.95,
      "citation": "https://...",
      "reasoning": "Multiple sources confirm..."
    }
  }
}

review_pass

Multi-pass synthesis review results.

{
  "pass_number": 1,
  "score": 0.72,
  "issues_count": 3
}

memory_recall

Prior knowledge recalled from the cross-session knowledge base.

{
  "memories_count": 5,
  "memories": [
    {"content": "Ethereum merged to PoS in Sep 2022", "tags": ["ethereum"]}
  ]
}

document

Document upload processing status.

{
  "filename": "report.pdf",
  "chars_extracted": 15000,
  "status": "processed"
}

usage

Terminal usage and cost telemetry, emitted near the end of a run. The meter snapshot is annotated with the pricing tier the request was billed at.

A retained run adds result_id only after saving its report successfully (null on a failed save). ZDR and playground runs do not acknowledge a saved result. Incremental snapshots with partial: true do not confirm persistence.

{
  "prompt_tokens": 48213,
  "completion_tokens": 9120,
  "total_tokens": 57333,
  "cost_usd": 0.412,
  "llm_calls": 24,
  "search_calls": 11,
  "source_count": 57,
  "cost_ceiling_usd": 1.5,
  "ceiling_hit": false,
  "by_model": {
    "bedrock/us.anthropic.claude-opus-4-7": {"calls": 1, "total_tokens": 18400, "cost_usd": 0.31}
  },
  "pricing": {
    "tier": "deep",
    "billed_as": "deep_krawl",
    "cost_ceiling_usd": 2.5,
    "x402_price_usd": 5.0
  }
}

changes_since_last

Auto-comparison with the previous result in the same session (emitted for follow-up research).

{
  "previous_result_id": "...",
  "has_changes": true,
  "new_findings": ["New finding 1"],
  "removed_claims": [],
  "changes": [{"metric": "price", "old_value": "$50k", "new_value": "$55k"}],
  "summary": "Since your last research: price increased..."
}

Event Order (Deep Mode)

Typical event sequence for a deep research session:

  1. session — session ID
  2. memory_recall — prior knowledge (if any)
  3. plan — research plan
  4. perspectives — STORM perspectives (if query is complex enough)
  5. node_started → query → source (repeated) → learning → node_completed (per tree node)
  6. gap_detected → recurse (depth-1)
  7. outline_updated
  8. report_chunk (many) — streaming synthesis
  9. review_pass — quality review
  10. chart — data visualizations (if applicable)
  11. structured_output — structured data (if output_schema provided)
  12. citation_verified (multiple)
  13. audit — full audit trail
  14. result — final result
  15. changes_since_last — comparison (if follow-up session)
  16. done — stream complete

JavaScript Client Example

const evtSource = new EventSource('/research', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'your-key',
  },
  body: JSON.stringify({ query: 'AI agents', mode: 'deep' }),
});

// Note: EventSource only supports GET. For POST, use fetch with ReadableStream:
const response = await fetch('https://api.krawl.sh/research', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'your-key',
  },
  body: JSON.stringify({ query: 'AI agents', mode: 'deep' }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop() || '';

  for (const line of lines) {
    if (line.startsWith('event: ')) {
      const eventType = line.slice(7);
      // Next line is data
    }
    if (line.startsWith('data: ')) {
      const data = JSON.parse(line.slice(6));
      console.log(eventType, data);
    }
  }
}

Evidence time and access

Research sources carry optional retrieved_at (UTC), published_date, updated_date, date provenance, access_status, source_record_id and temporal_status. Missing fields on older reports mean unknown; never assign export time as retrieval time. Access states are inspected_text, partial_excerpt, provider_summary, metadata_only, fetch_failed, or unknown. These describe available evidence, not claim support or truth. A recent fetch does not make an old article current. Summary/metadata-only results do not establish that the original text was read.

Optional source_policy.as_of sets a UTC evidence cutoff; dates after it are quarantined in quarantined_sources, outside synthesis. Without it the cutoff is research start. This is not a historical web archive; undated content remains uncertain. See evidence methodology.

Evidence coverage

The optional evidence_coverage result field gives descriptive counts of unique exact URLs discovered, explicitly fetched, retained, referenced in outgoing model input, and cited. Stages overlap: a retained search snippet may never have had its original page fetched. It also reports explicit failed/unavailable fetches, unknown fetch/access states, retained-host concentration, missing publication dates and their known range. A model-input reference may contain only metadata or a short excerpt; a citation is not a support verdict.

These counts do not establish representative sampling or correct selection bias. Hostnames do not prove independent origins; mirrors may share a publisher. Old reports without this field have unknown coverage. No extra LLM call is made; the measured instrumentation overhead is reported as measurement_overhead_ms, not a guarantee of zero added latency. See evidence methodology.

On this page