# How Vane Provides Fully Cited Answers: A Technical Architecture Breakdown

> Discover Vane's technical architecture. Learn how XML tags and strict prompts enable Vane to generate fully cited answers and enforce citation requirements for factual statements.

- Repository: [Kushagra Srivastava/Vane](https://github.com/ItzCrazyKns/Vane)
- Tags: architecture
- Published: 2026-03-11

---

**Vane generates cited answers by wrapping search results in XML-style tags that identify citable sources to the LLM, then enforcing strict citation requirements through a specialized writer prompt that mandates [number] notation for every factual statement.**

Vane is an open-source AI search engine developed by ItzCrazyKns/Vane that distinguishes itself by returning answers with verifiable sources. Unlike standard chatbots that may hallucinate citations, Vane implements a tightly-coupled pipeline ensuring every factual claim traces back to a specific retrieved web source. This article examines the three core technical components that enable Vane's guarantee of fully cited answers.

## Search Context Wrapping with XML Tags

The foundation of Vane's citation system lies in how the `SearchAgent` formats raw search results before they reach the language model. In [`src/lib/agents/search/index.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/agents/search/index.ts) (lines 15-16), the system transforms each search finding into structured XML-like tags that explicitly signal which content is safe to cite.

```typescript
const finalContext = searchResults?.searchFindings
  .map((f, i) => `<result index=${i + 1} title=${f.metadata.title}>${f.content}</result>`)
  .join('\n') || '';

const finalContextWithWidgets = `<search_results note="These are the search results and assistant can cite these">
${finalContext}
</search_results>
<widgets_result noteForAssistant="Its output is already showed to the user, assistant can use this information to answer the query but do not CITE this as a souce">
${widgetContext}
</widgets_result>`;

```

This wrapping strategy serves two critical functions. First, the `<search_results>` container tells the downstream LLM that every child `<result>` element represents an authoritative source eligible for citation. Second, the separate `<widgets_result>` tag identifies auxiliary data that the model may use for context but must not cite as a primary source. This distinction prevents the LLM from generating phantom citations to widget data or internal calculations.

## Citation-Aware Prompt Engineering

Once the search context is wrapped, Vane injects a specialized system prompt that forces citation compliance. The `getWriterPrompt` function in [`src/lib/prompts/search/writer.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/prompts/search/writer.ts) (lines 13 and 24-30) contains explicit instructions that override the LLM's default behavior.

```typescript
return `
...
- **Cited and credible**: Use inline citations with [number] notation to refer to the context source(s) for each fact or detail included.
...

### Citation Requirements

- Cite every single fact, statement, or sentence using [number] notation corresponding to the source from the provided \`context\`.
- Integrate citations naturally at the end of sentences or clauses as appropriate. For example, "The Eiffel Tower is one of the most visited landmarks in the world[1]."
...
<context>
${context}
</context>
...
`;

```

The prompt employs a **"Citation Requirements"** section that mandates the `[number]` inline notation and provides concrete syntax examples. By instructing the model to "cite every single fact," the prompt eliminates ambiguity about when attribution is necessary. This strict prompt engineering ensures that even when processing the combined system prompt and user query, the LLM treats attribution as a non-optional formatting requirement rather than a stylistic suggestion.

## LLM Streaming and Block Management

After receiving the citation-heavy prompt, the LLM generates the answer through a streaming interface that preserves the structured citations. The implementation in [`src/lib/agents/search/index.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/agents/search/index.ts) (lines 38-65) captures each token stream into a durable text block, ensuring the fully cited response is stored incrementally without losing formatting.

```typescript
for await (const chunk of answerStream) {
  if (!responseBlockId) {
    const block: TextBlock = { id: crypto.randomUUID(), type: 'text', data: chunk.contentChunk };
    session.emitBlock(block);
    responseBlockId = block.id;
  } else {
    const block = session.getBlock(responseBlockId) as TextBlock | null;
    if (block) {
      block.data += chunk.contentChunk;
      session.updateBlock(block.id, [{ op: 'replace', path: '/data', value: block.data }]);
    }
  }
}

```

This streaming loop utilizes the `session` object (managed in [`src/lib/session.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/session.ts)) to emit and update `TextBlock` instances. By appending each `chunk.contentChunk` to the block's data property, Vane ensures that the final stored answer contains the complete set of inline citations generated by the model. The `crypto.randomUUID()` utility from [`src/lib/utils.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/utils.ts) provides unique identifiers for tracking these blocks across the conversation lifecycle.

## Summary

Vane's architecture creates a closed feedback loop that guarantees cited answers through three coordinated mechanisms:

- **Source Attribution Wrapping**: XML-style tags in [`src/lib/agents/search/index.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/agents/search/index.ts) explicitly mark which retrieved content is citable versus auxiliary widget data.
- **Mandatory Citation Prompts**: The writer prompt in [`src/lib/prompts/search/writer.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/prompts/search/writer.ts) forces the LLM to use `[number]` notation for every factual statement.
- **Durable Response Storage**: The streaming block management system preserves the LLM's citations by incrementally building the final answer in `TextBlock` objects.

Together, these components ensure that every answer generated by the ItzCrazyKns/Vane repository maintains traceability to its original web sources.

## Frequently Asked Questions

### How does Vane prevent the LLM from hallucinating citations?

Vane prevents hallucinated citations by strictly controlling the context window. The `<search_results>` wrapper in [`src/lib/agents/search/index.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/agents/search/index.ts) provides an enumerated list of valid sources, and the writer prompt explicitly forbids referencing external knowledge. The LLM can only cite content found within the numbered `<result>` tags, making fabrication easily detectable and preventable through the constrained context design.

### What is the difference between search_results and widgets_result tags?

The `<search_results>` tag contains web search findings that the LLM is explicitly allowed to cite using `[number]` notation, while the `<widgets_result>` tag holds pre-processed widget outputs (such as calculations or tool results) that the model may reference for context but must not attribute as a source. This separation, implemented in the `SearchAgent`, ensures that computational outputs are not mistakenly presented as citable web sources.

### Can the citation format be customized in Vane?

Currently, the citation format is hardcoded in the writer prompt at [`src/lib/prompts/search/writer.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/prompts/search/writer.ts). The system mandates `[number]` notation with specific syntax examples. While the codebase supports modifying the prompt template, the streaming block management and frontend rendering systems expect this specific format, so changes would require corresponding updates to the citation parsing logic throughout the application.

### How does Vane handle long answers with multiple citations during streaming?

Vane manages long cited answers by maintaining a persistent `responseBlockId` throughout the streaming process. As documented in [`src/lib/agents/search/index.ts`](https://github.com/ItzCrazyKns/Vane/blob/main/src/lib/agents/search/index.ts) (lines 38-65), the system creates a single `TextBlock` at the start of the stream and incrementally appends each chunk using `session.updateBlock()`. This approach ensures that even lengthy responses with dozens of inline citations are captured as a single coherent message without fragmentation or loss of citation markers.