Get started

Engineering NotesMemory and search

Every answer source is a URL, and the server builds the list

When one answer draws on web pages, internal documents, records queried from a database, and tickets returned by a connector (MCP), deciding how to cite them becomes a real problem. The common approach numbers each tool result and has the model put markers such as [1] in the text. Now take an answer that shows 14 incident tickets in a table, all from a single database query. The 14 tickets share one number, and a user who clicks it has no way to tell which ticket it should open.

In OwlAI, the model knows one citation rule: write the URL of the material you relied on as a link in the text. The server builds the source list and unwraps links that must not stay, using fixed rules. This post covers why every source comes down to a URL, the order in which the server builds the list, and why we check whether a model-written URL is genuine on outbound requests rather than in the answer text.

Evidence numbers point to tool results, and users open pages

Evidence numbers have real advantages. Each number is tied to one tool result, so the server can trace which sentence relies on which result. The format is also familiar from AI search services.

The trouble is that the number points to a tool result. One web search hit is one page, so the number and the page line up. A database query result is different: it has many rows, and the result itself has no address to open. Giving each record its own marker ([R1]) means two numbering schemes. The model has to keep both straight, and the server needs a separate rule for each marker type to check it against the actual tool results. On screen, where a click takes you depends on the marker type.

Users do one thing with a source: they click it and open the original. A marker with nowhere to go does nothing when clicked. So we made the unit of a source an address that can be opened, not a tool result.

Every source a user can click comes down to an address

An answer carries four kinds of source, and each can become an address:

  • Web pages and connected-service results already have URLs.
  • Database records become the address of a detail page in an internal business system through rules an administrator registers on the Shortcut links screen. A rule is a detail-page address template plus a description of when to use it. The model fills an order number or ticket key into the template's value slot to build the address.
  • Internal documents arrive in document search results with an in-app document address (/documents/<document ID>). The address has no host, so the same document opens in the in-app preview whether you open it on the web or in the desktop app.

To the model, all four are plain URLs. It has no need to know about evidence numbers, record markers, or source types. In the answer with 14 incident tickets in a table, each of the 14 ticket keys links to its own detail page.

Four kinds of sources converge on one URL and become underlined links and source chips
Figure 1. Four kinds of sources converge on one URL and become underlined links and source chips

The model writes links in one of two forms, and the screen decides how each looks from the syntax alone.

Syntax How it renders When to use it
[text](url) An underlined link on that text The value itself should be clickable: an order number, ticket key, or document name
<url> or a bare URL A source chip at the end of a sentence (a small button with the site icon and page title) A web page that backs that sentence

Inside a table, a link renders as an underlined link even if only the address was written. That keeps it consistent with the cells of a table that becomes a table widget once the answer is finished.

We chose syntax over rules that pick the form from where a link sits in a sentence or which tool it came from. A guessing rule such as "a link at the end of a sentence becomes a chip" has to be fixed again for every edge case, like an answer that puts a source mid-sentence or right below a table. Syntax is exactly what the model wrote, so the screen and the model follow the same rule.

The prompt tells the model to copy URLs from tool results without changing them, and when there is no real URL, to make no link and say so.

The server builds the source list from the final answer

Once the answer is final, server-side post-processing builds the "Sources" list. The model is never asked to rewrite the answer, and the same answer always yields the same list. Chat, deep research, and playbook runs all go through the same post-processing, in this order:

  1. Collect the links in the text and the link cells of table widgets in order of first appearance. Addresses inside code blocks are not treated as links.
  2. Merge links that share an address. The list holds up to 100 entries; links beyond that stay only in the text.
  3. Pick a title for each entry: the link text first; if that is empty, the page title or document name from this turn's tool results (a turn is one question and the answer to it); failing that, the host name.

Title selection has one exception. If the same address appears even once as a source chip, the link text is not used as the title. When a link on a value, such as [ORD-1234](url), shares an address with a chip, the chip would otherwise be titled "ORD-1234." In that case the title falls through to the page title or host name.

Links that must not stay are unwrapped, leaving only their text. One kind is a link that is neither a web address nor an in-app document address. The other is a reference to a masked transfer address. Pre-authenticated download addresses returned by connectors contain access tokens, so the server swaps each one for a short reference before the model sees it. Turning that reference back into a link in the answer would mean restoring the original address, which would leave the token in the answer text and the database. So it is not made into a link.

An underlined link, a source chip, and the Sources list in one answer
Figure 2. An underlined link, a source chip, and the Sources list in one answer

URLs in the answer text are not filtered by character matching

Checking, character by character, whether each URL the model wrote actually appeared in a tool result looks like a way to stop invented links. Tracing where 5,400 URLs came from, across the answer texts of 949 turns observed in production, shows what that filter would cost.

91% were verbatim from tool results. 330 were legitimately assembled: 249 the model built by following a service's conventions, such as an issue tracker's "address/browse/key," and 81 built from the address templates of shortcut link rules. Broken or invented addresses numbered about 50, or 0.9%.

A character-matching filter would delete those 330 assembled links to catch the 0.9%. Shortcut links in particular exist to create addresses that never appear in tool results, so a matching filter collides with them head-on. We also left out normalization that reconciles host names. Every installation has a different public address, and there is no setting that says which host is right.

Authenticity is checked on outbound requests

Links in the answer text and URLs that tools open carry different risks. A user sees a link in the answer before clicking it. A URL a tool opens becomes an outbound request with no user confirmation. If an instruction hidden in a document (indirect prompt injection) gets the model to build an address carrying conversation content, that request alone leaks the conversation.

So URL checks live where requests go out. Tools that read a web page or fetch a file from a URL open only addresses that already appeared in the conversation, and do not let the model append values to them (the rules are in “A URL in a tool result stays closed if the model wrote it first”).

Site icons on source chips are restricted for the same reason. If the screen fetched each site's icon directly, the user's IP address and the domain of the page they viewed would go to that site. Fetching the icon for any address the model wrote would also let an injection carry conversation content out in a host name. So the screen loads no external images and asks the OwlAI server for icons instead. The server attaches icons only to addresses that appeared in this turn's web search or web extraction results, and fetches them per domain from one fixed icon service, caching the result. Every other link gets the default globe icon.

Design choices

We put no character-matching filter on links in the answer text. The cost is that a broken link occasionally stays in the text. Because users see these links before clicking them, we accepted that cost and put address checks on requests that go out without user confirmation.

We defined a source as "whatever the text links to." Only material the answer actually relies on goes into the list. Material that was queried but never linked does not appear there; what was queried shows up separately in the tool steps under Thinking.

This definition has a weakness: when the model drops a link, the source drops with it. In deep research, a researcher can write an exact link in its report, and if the lead does not carry it into the final report, it does not reach the list. The server does not fill in missing links, because if the lead deliberately left out a finding, its link should go too. Instead, the prompt tells the lead to carry over links on values that open records, table cells included, and every turn logs how many researcher links the final report did not carry over, to show how well that instruction holds.

If your agent's tools return sources that all look different, this approach carries over directly. First find a way to give each source an address that can be opened, give the model one rule to write links, and let the server build the list and the titles. Check whether an address is genuine where a request to that address goes out.