Skip to Content
Overview

Overview

Rosetta is a clinical writing app in the browser. You draft structured notes in an editor that enforces your templates. Alongside it, AI agents answer questions against your own reference material, propose edits you review before they apply, and call clinical tools such as terminology lookups and score calculators.

Main Areas

Editor

The editor is built on Lexical. Your note is displayed in plaintext, with extra nodes for template anchors, fields, and suggestion previews. Agent edits arrive as pending suggestions you accept or reject, and locked template sections can’t be modified by agents.

Workflows and Agents

Workflows are presets for note actions such as case summaries, Assessment & Plan drafts, and checklists. They run from the right sidebar and return output to chat. Selecting text and right-clicking queues agents to run on just that selection.

The agents can also call clinical tools during a chat turn: ICD-10 , LOINC , and RxNorm  lookups, PubMed  search, OpenFDA  and clinical trial  queries, and clinical score calculators.

See Workflows for workflow and agent behavior.

RAG

Uploaded files, pasted text, saved account sources, and PubMed results all feed retrieval. The retriever pulls sources into an answer and formats citations as [Source N].

See RAG for retrieval behavior.

Bring Your Own Key

Model requests go directly to the providers, using keys you supply in Settings. Google (Gemini) , OpenAI , Anthropic , DeepSeek , and OpenRouter  are supported, and Tavily  can be added for live web search.

Getting Started

Add a provider key, open a note, and ask the assistant for anything.

Prerequisites

  • A current version of Chrome, Firefox, Safari, or Edge.
  • An API key for at least one supported provider. A Google Gemini key covers the default model path and retrieval.

First Time Setup

1. Add a Provider Key

Open Settings → AI Models and paste a key for the provider you use:

Provider key formats
Google (Gemini) AIza... OpenAI sk-... Anthropic sk-ant-... DeepSeek sk-... OpenRouter sk-or-...

Keys can be saved or removed per provider. Without a key the editor still works, but AI features will not run.

2. Add a Tavily Key for Web Search (Optional)

Live web search runs through Tavily when the assistant needs current information that is not in your sources or PubMed. Add the key under Settings → Tavily web search. You can also provide it through the VITE_TAVILY_API_KEY environment variable for local development. Without a Tavily key the rest of the app works normally, and the assistant skips web search.

3. Sign In (Optional)

Signing in syncs your settings, scheduled tasks, and workspace records across browsers. Signed out, everything stays in the browser’s local storage on that machine.

4. Learn the Interface

  • Left sidebar: Projects, Templates, Smart Phrases, and Slash Commands.
  • Center: the note editor, with a tab strip showing open notes, the autosave timestamp, save history, and the template menu.
  • Right sidebar: Revisions, Workflows, RAG sources, and Reasoning.

Basic Workflow

  1. Create a project, or open one. Each project carries a specialty and a care context (Internal Medicine and Outpatient by default) that shape agent output for its notes.
  2. Open a note and apply a template from the template menu if you want a fixed structure.
  3. Write. Autosave runs continuously, and the timestamp in the tab strip shows the last save.
  4. Add reference material in the RAG panel to let answers cite your own sources.
  5. Ask the assistant in chat, or select text and right-click to run an agent on just that selection.
  6. Review each pending suggestion in the editor and accept or reject it. Nothing changes your note until you accept it.

For a worked example of steps 4 through 6, see First Document.

Synthetic Cases

You do not need a real chart. Ask the assistant in chat to create synthetic cases and it will draft the notes and store them as a project:

Chat prompt
Create a project of 5 synthetic inpatient cases for internal medicine, each with an H&P and a progress note.

This is useful for trying templates, testing presentations, or building teaching material.

Keyboard Shortcuts

ShortcutAction
Ctrl/Cmd + SSave the current note, or the template in the template editor
Ctrl/Cmd + WClose the current note tab
EscClose the settings panel
/Slash commands in the editor

Smart phrases expand as you type. For example:

Smart phrase
.hpi → History of Present Illness:

Press Enter or Tab to accept the expansion, or Esc to dismiss it.

If Something Goes Wrong

Most problems trace back to a missing key or an unreadable file. See Troubleshooting for the common errors and their fixes.

First Document

Save History

Your notes save automatically as you type. The timestamp in the note tab strip shows the last save. Manual saves (Ctrl/Cmd + S or the save button in the tab strip) create named snapshots. Open the history menu in the tab strip to restore, rename, or delete a snapshot. Restoring does not discard your current draft’s history.

Right-Click Actions

Select text and right-click to run an agent on just that selection: expand shorthand, reformat, reason through it, or pull citations. The agent returns a pending suggestion. Your original text stays until you accept or reject it, and suggestions that touch locked template sections are dropped.

For example, selecting this and running the Shorthander:

Selected text
55yo M c CP, SOB x2d

produces a pending suggestion like:

Pending suggestion
55-year-old male with chest pain and shortness of breath for two days

RAG Workflow

Add a source in the RAG panel first. Click Add Source, give it a name, and paste or upload content:

RAG source
Name: Sepsis Guidelines 2024 Content: - Draw blood cultures before antibiotics - Start broad-spectrum coverage within 1 hour - Piperacillin-tazobactam 4.5 g IV q6h - Add vancomycin if MRSA risk factors

Each source has an “included in ingest” toggle, and only included sources are searched.

Then ask the assistant a clinical question in chat:

Chat prompt
What antibiotics should I start for suspected sepsis?

It retrieves the relevant passages from your included sources (and PubMed, if you use the PubMed search), answers with [Source N] citations inline, and lists the cited sources under the response.

Troubleshooting

Most problems come down to a missing key or an unreadable file. This page lists the errors you are most likely to hit and what to do about each one.

No AI API Key Configured

Error
No AI API key configured. Add one in Settings.

The assistant has no provider key to work with. Open Settings → AI Models and add a key for any supported provider.

Tavily API Key Is Missing

Error
Tavily API key is missing. Set it in Settings or VITE_TAVILY_API_KEY.

This appears only when the assistant tries to run a web search and no Tavily key is set. Add one under Settings → Tavily web search, or set VITE_TAVILY_API_KEY for local development. Web search is optional — you can ignore this and rely on your own sources and PubMed.

Empty or Failed Responses

A request that returns nothing usually means the provider key is missing, invalid, or out of quota. Check the key in Settings first. If the key is valid, the provider itself may be rate limiting or temporarily unavailable, in which case waiting and retrying is the fix.

PDF Text Extraction Failed

Error
[PDF uploaded - automatic text extraction failed. Convert to text before ingesting.]

PDFs are parsed in the browser, but scanned PDFs, or PDFs that contain only images, have no selectable text to extract. The source is still stored, but it will not enter retrieval. Convert the document to text, or paste the relevant passages in as a text source instead.

A Revision Skipped Part of Your Request

On a note with a locked template, suggested changes that conflict with a locked section anchor are dropped. Unlock the template or rephrase the request to target an editable section.

Settings or Tasks Are Not Syncing

Sync requires an account. Signed out, your workspace lives only in the browser’s local storage on the current machine, and scheduled tasks will not run in the background.

The App Will Not Start Locally

Install dependencies and start the dev server from the project folder:

Terminal
cd rosetta npm install npm run dev

The dev server expects its port to be free. If another process is using it, stop that process or start the dev server on a different port.

Frequently Asked Questions

What is Rosetta?

Rosetta is a clinical text editor with agents built in. You write notes as usual. Agents do the tedious work, including reformatting and pulling citations. Every change an agent proposes has to be accepted before it modifies your document.

How do I access the platform?

Sign in at philipshih.org/apps/rosetta . Account creation is limited during the beta.

Which AI models can I use?

No AI access is built in — you supply your own provider keys. Add them in Settings → AI Models, and supported AI features use whichever providers you configure. Google (Gemini), OpenAI, Anthropic, DeepSeek, and OpenRouter are supported. Web search uses a separate Tavily key, set under Settings → Tavily web search.

Where does my data live?

Signed out, everything stays in the browser’s local storage; clearing site data clears your workspace. Signed in, settings, scheduled tasks, and durable workspace records sync to your account, and notes are also cached locally.

Prompts, retrieved source passages, and the note content involved in a request are sent to whichever model provider handles that request.

What can I upload into RAG?

The RAG panel accepts PDFs, plain text, Markdown, JSON, CSV, TSV, HTML, RTF, and common image formats. You can also search PubMed in the app.

What is the difference between Plan and Act?

Plan reasons through a request first. Act returns an answer, draft, or edit right away.

How do templates work?

Templates come in three scopes: shared templates, workspace defaults, and local overrides for one note. A note with a template applied is locked to that structure: section anchors constrain AI edits, and locked anchors can’t be modified by agents.

How do I use chat tools?

Ask a question in your own words in the chat panel, or select text and right-click to run an agent on just that selection: expand shorthand, reformat, reason, or cite.

What is automatic approval?

By default, agent actions that change your workspace pause for your approval. Settings has an “Auto-approve agent actions” toggle that skips the confirmation step. Leave it off until you trust how agents behave on your own material.

Where are my edits?

The edit arrives as a suggestion first. You review it in the editor and accept or reject it. Only accepted suggestions enter revision history.

What appears in revisions?

The Revisions tab shows accepted AI changes. Each entry records the original text, the accepted text, the reason, and the time accepted.

What literature sources can agents pull from?

PubMed, through the NCBI E utilities APIs — selected articles are added as RAG sources. Agents can also query OpenFDA and clinical trial registries during a chat turn, and run a live web search through Tavily when a question needs something outside your library.

Are import and export supported?

Yes. Paste or upload raw clinical text to have it segmented into projects and notes — split on your configured delimiter, or let the model find the case boundaries. For export, a folder’s notes can be downloaded as a single text file, and RAG sources can be downloaded individually.

Editor

The editor is built on Lexical 0.39. A note is stored as a tree of editor nodes. Agent edits appear as pending suggestions first. You accept or reject each suggestion before it changes the note. Locking a template section blocks agents from writing to it.

Components

AreaWhat it does
EditorCoreMounts Lexical, tracks selection, and dispatches commands.
LexicalNoteEditorThe active editor runtime: sets up the composer, owns the live Lexical editor instance, and bridges debounced snapshots back to app state. NoteEditorV2 remains as a compatibility wrapper around it.
TemplateManagerHolds template anchors and fields. Blocks writes to locked regions.
SuggestionOverlayRenders pending agent diffs in the editor and routes review to the revisions surface.
SyncBridgePersists debounced plain text and Lexical JSON snapshots, with blur and unload flushes.

Core Rules

  1. All document mutations go through editor.update(). Every write is transactional and observable.
  2. Lexical is the live typing source of truth. React note state and persisted JSON are snapshots.
  3. Agents and automation cannot modify locked template anchors.
  4. Each suggestion is tagged with the revision it was generated against. If the user edits in between, the suggestion’s range is remapped onto the new revision, or marked stale if remapping can’t resolve it.
  5. Undo/redo only covers changes that actually landed. Rejected and stale suggestions never wrote to the document.
  6. Every applied change, whether from a user or an agent, emits the same change event. Sync, logging, and other subscribers read from one stream.

Document Model

Documents are serialized as node trees:

Serialized document
interface SerializedEditorState { root: { type: "root"; version: number; children: SerializedNode[]; direction: "ltr" | "rtl" | null; }; } interface SerializedNode { type: string; version: number; children?: SerializedNode[]; text?: string; format?: number; detail?: number; mode?: "normal" | "segmented" | "token"; }

Node Types

Custom nodes
class RosettaTextNode extends TextNode { __format: number; } class RosettaParagraphNode extends ParagraphNode { __indent: number; } class AnchorNode extends ElementNode { __label: string; __isLocked: boolean; } class FieldNode extends DecoratorNode<JSX.Element> { __fieldType: "SELECT" | "MULTISELECT" | "DYNAMIC_SELECT"; __options: string[]; }

Change Tracking

Changes land in two separate records:

  • Lexical history stack: what undo/redo walks through. Only contains changes that were applied.
  • Operational log: what sync and agent bookkeeping read from. Contains every transaction with its source, revision, and ranges.

Transaction Shape

ChangeTransaction
type ChangeSource = "user" | "ai" | "template" | "sync"; type ChangeKind = "insert" | "replace" | "delete" | "format"; interface ChangeTransaction { id: string; revision: number; parentRevision: number; source: ChangeSource; kind: ChangeKind; createdAt: number; ranges: Array<{ start: number; end: number; beforeText: string; afterText: string; anchorId?: string; }>; metadata?: { suggestionId?: string; agent?: "chat" | "shorthander" | "reasoner" | "reformatter" | "citations" | "ingester"; batched?: boolean; }; }

Keystroke to Commit Flow

During typing, one editor snapshot is computed per Lexical update.

Diffs

The old note and the new note are compared in three passes. Each pass only runs if the previous one did not produce a reliable result:

  1. Nodes: compare editor subtrees by identity. If a whole paragraph or template block did not change, the pass skips it.
  2. Tokens: compare sentence sized chunks or template chunks. Most clinical edits stop here because this is accurate enough for review.
  3. Characters: compare individual characters when token precision drops below 98%. This lets the overlay highlight the exact text that changed.
computeRanges
function computeRanges(prev: string, next: string): RangeDelta[] { const tokenPass = diffByTokens(prev, next); if (tokenPass.precision >= 0.98) return tokenPass.ranges; return diffByCharacters(prev, next).ranges; }

Range Remapping

A suggestion points at specific character offsets. If the user types before the suggestion is applied, those offsets can point at the wrong place. Each offset is shifted by the net length change of every user edit that happened at or before it:

remapRange
function remapRange(range: { start: number; end: number }, deltas: RangeDelta[]) { let { start, end } = range; for (const delta of deltas) { if (delta.pos <= start) start += delta.netLength; if (delta.pos < end) end += delta.netLength; } return { start, end }; }

Overlap and Conflict Rules

Agent Suggestion Lifecycle

Suggestion Object

AISuggestion
type SuggestionStatus = | "pending" | "focused" | "accepted" | "rejected" | "stale" | "superseded"; interface AISuggestion { id: string; revision: number; type: "replace" | "insert" | "delete"; start: number; end: number; replacementText: string; reasoning: string; confidence: number; status: SuggestionStatus; }

Suggestion State Machine

Safe Apply Logic

applySuggestion
function applySuggestion(suggestion: AISuggestion) { editor.update(() => { if (isLockedRange(suggestion.start, suggestion.end)) return; const remapped = remapSuggestionAgainstLatestRevision(suggestion); if (!remapped) { markSuggestion(suggestion.id, "stale"); return; } replaceText(remapped.start, remapped.end, remapped.replacementText); appendChangeTransaction({ source: "ai", kind: suggestion.type === "insert" ? "insert" : "replace", metadata: { suggestionId: suggestion.id } }); markSuggestion(suggestion.id, "accepted"); }); }

Visual Indicators

TypeColorBehavior
InsertGreenPreview as additive text
ReplaceYellowOriginal highlighted with replacement preview
DeleteRedOriginal shown with remove indicator

Template Anchors

TemplateAnchor
interface TemplateAnchor { id: string; label: string; isLocked: boolean; nodeKey: string; start: number; end: number; }

Guard Rails

  • Agents cannot modify locked anchors.
  • Bulk accept skips suggestions touching locked anchors.
  • Template updates can move anchors but cannot silently unlock them.
  • Anchor deletion requires explicit user confirmation.

Template Fields

SELECT single choice:

SELECT field
{{SELECT: medication | aspirin, clopidogrel, warfarin}}

MULTISELECT multiple values:

MULTISELECT field
{{MULTISELECT: symptoms | chest pain, dyspnea, diaphoresis}}

DYNAMIC_SELECT options resolved from query:

DYNAMIC_SELECT field
{{DYNAMIC_SELECT: drug | query: medications for hypertension}}

Field Node Rendering

FieldNode
class FieldNode extends DecoratorNode<JSX.Element> { decorate(): JSX.Element { return ( <FieldComponent type={this.__fieldType} options={this.__options} onSelect={this.handleSelect} /> ); } }

SmartPhrases

Expansion rules for common note sections:

Smart phrases
const smartPhrases: SmartPhrase[] = [ { trigger: ".cc", expansion: "Chief Complaint:\n" }, { trigger: ".hpi", expansion: "History of Present Illness:\n" }, { trigger: ".pe", expansion: "Physical Examination:\n" }, { trigger: ".ap", expansion: "Assessment & Plan:\n" } ];

Expansion is treated as a user transaction. Pending agent suggestions touching the trigger span are remapped, and if remap fails they are marked stale.

Plugin Order

Plugin order
<LexicalComposer initialConfig={config}> <RichTextPlugin /> <HistoryPlugin /> <RosettaStateSyncPlugin /> <TemplateLockPlugin /> <TemplateReconciliationPlugin /> <EditorCommandPlugin /> <ClinicalHighlightPlugin /> <IndentGuidesPlugin /> </LexicalComposer>

RosettaStateSyncPlugin owns the external load path and the shared editor snapshot. EditorCommandPlugin owns keyboard commands, suggestion commands, and template commands. Clinical highlighting and indent guides run after editor mutations, but both are scheduled outside the immediate typing path.

Data Model

Documents are stored as Lexical node trees. The live node tree is the source of truth while the user is typing. Plain text and persisted JSON are snapshots. Everything else, including pending edits, template anchors, and sync payloads, references positions inside it.

Core Entities

  • Document nodes: block containers such as paragraphs and headings, plus inline marks such as bold, italic, and code.
  • Pending edits: changes proposed by an agent. They live outside the document until the user accepts them.
  • Template anchors: named regions defined by a template.
  • Editor snapshot: the plain text, selection offsets, cursor position, slash command context, SmartPhrase context, and context menu geometry emitted from a single Lexical update listener.

Persistence Shape

The app persists two synchronized views of the same document:

  • content: plain text used by workflows, search, exports, and legacy integrations.
  • jsonContent: serialized Lexical state used to restore decorators, template fields, suggestions, and editor metadata.

Typing does not regenerate jsonContent on every keystroke. The live Lexical tree updates immediately, a lightweight snapshot goes out to UI consumers, and content and jsonContent are written after the persistence debounce or an explicit flush boundary.

Pending Edit Structure

A pending edit is a proposed change that has not touched the note yet. It stores the exact range it applies to, the original text, the replacement text, the agent’s reason, and the agent that produced it. Because those details travel with the edit, the editor can show the preview, record the audit trail, compare it with other suggestions, or mark it stale without changing the document.

PendingEdit
interface PendingEdit { id: string; type: "insert" | "replace" | "delete"; start: number; end: number; original: string; replacement: string; reasoning: string; agent: string; }

For the full lifecycle, see the Editor Guide.

Keyboard Shortcuts

Workspace

KeyAction
Ctrl/Cmd + SSave the current note, or the template when the template editor is open
Ctrl/Cmd + WClose the current note tab, or the template editor
EscClose the settings panel

Editing

KeyAction
Ctrl/Cmd + ZUndo
Ctrl/Cmd + Shift + ZRedo
TabIndent the current block, or each block when several are selected
Shift + TabOutdent
/Open slash commands

Smart Phrases

KeyAction
Enter or TabAccept the suggested expansion
EscDismiss the suggestion

Workflows

Document State

Document state lives on your device first. Keystrokes hit the live editor and the browser’s local store immediately. Signed in, notes and workspace records sync to your account in the background. Signed out, everything stays in local storage on that machine.

Review Workflow

Nothing an agent produces changes a note until you review and accept it.

Typical Note Path

  1. open the note
  2. apply the template
  3. draft or edit
  4. run chat or a workflow
  5. review the suggestion
  6. accept it to revise the note

Workflow results return to chat unless you accept an edit. Rejected suggestions leave the note unchanged.

State Rules

  • Opening a note restores the current note content and template state.
  • Suggestions remain pending until the user accepts or rejects them.
  • Accepting a suggestion updates the note and creates a revision entry.

Approval Gate

Agent actions that change your workspace pause for confirmation before they apply. The “Auto-approve agent actions” toggle in Settings skips this confirmation step.

Folder Workflow Path

Folder presentation workflows follow a separate branch:

  1. open the folder
  2. collect notes
  3. run the presentation workflow
  4. return the draft to chat

These workflows do not require a single active note, but they do depend on notes existing in the selected folder.

Templates

Templates define note structure and field behavior.

Interactive Fields

  • Text fields: {{chief_complaint}}
  • Select menus: {{SELECT:severity|mild,moderate,severe}}
  • Dates: Date pickers that format the date for you

Locked Anchors

Template regions that agents can’t write to: legal disclaimers, required section headers, billing codes.

Agents

Run agents from chat or by right-clicking selected text. Each run produces a pending suggestion.

AgentWhat it does
ShorthanderExpands medical abbreviations (SOB becomes shortness of breath). Skips locked anchors.
IngesterParses uploaded PDFs and text files into searchable chunks. Keeps the original metadata for tracing any chunk back to its source.
ReasonerGenerates ranked differentials and plans organized by problem. Weighs claims that have retrieved evidence over ones that don’t.
ReformatterRewrites selected text to match a requested format (SOAP, bullet list, table). The meaning stays the same.
CitationsSearches PubMed and attaches references to the relevant claims.

Clinical Tools

During a chat turn the assistant can also call clinical tools directly:

Tool familyCoverage
Terminology lookupsICD-10, LOINC, and RxNorm
Literature and regulatory dataPubMed, OpenFDA, and clinical trial registries
Scores and calculationsCommon scoring systems and clinical math, computed in code rather than by the model

For example, asking:

Chat prompt
What's the ICD-10 code for community-acquired pneumonia, and calculate the CURB-65 for this patient?

resolves the code through the terminology lookup and computes the score from the values in your note.

SmartPhrases

Custom text expansion shortcuts. Type the trigger and accept the expansion with Enter or Tab:

TriggerExpansion
.ccChief Complaint section
.hpiHistory of Present Illness
.rosReview of Systems
.pePhysical Examination
.apAssessment & Plan
.dcDischarge instructions

Slash Commands

Slash commands are custom commands you define and invoke with / in the editor. Each command has a label, a trigger, and options, managed from the Slash Commands panel in the left sidebar. With “AI auto-fill slash defaults” enabled in Settings, highlighting text before creating a command fills those fields in from the selection.

Template Pipeline

Templates define both the structure of a note and the rules it has to follow. They load from a registry, apply at workspace or note scope, and enforce their anchors in the editor; edited variants can be saved back to the template library.

Template Sources

SourceWhat it is
built in templatesTemplates shipped with the app
user templatesTemplates saved in versioned local storage
workspace templateDefault template selected for the current workspace session
local note templateA local override of the template anchor set

Pipeline Steps

  1. Select template: load the structure and its anchor and field definitions.
  2. Bind field values: resolve SELECT, MULTISELECT, and DYNAMIC_SELECT inputs and insert them at their anchors.
  3. Run agent transforms: agents propose edits against the bound document.
  4. Validate locked regions: any proposed change that overlaps a locked anchor is dropped before it reaches the review queue.
  5. Queue pending edits: remaining suggestions wait for the user to accept or reject them.

Global vs Local Template Modes

ModeWhat it is
globalThe note follows a shared template definition
localThe note stores its own anchor list and optional custom name

Local mode is useful when a note needs custom headings but you do not want to change the shared template for every future note.

Template Inference

Template state can be inferred from the note content itself. If the note headings match a known template anchor sequence, the app attaches that template state. If headings no longer match, it clears or replaces the template association.

Template Editor

The template editor supports two formats:

FormatWhat it is
naturalTemplate content with readable headings
smartphraseSmartPhrase content with a reusable trigger

When you save template edits, you can:

  • overwrite the current template
  • save a new template
  • store SmartPhrase metadata alongside the template
  • save the SmartPhrase into the SmartPhrase registry

RAG

Answers draw on retrieved sources: your uploads, your account library, public medical databases, and live web search.

Architecture

Source documents are split into passages, and each passage is converted into a vector that represents its meaning. A query is turned into a vector the same way. Retrieval returns the passages whose vectors are closest to the query vector (by cosine similarity), combined with keyword scoring that still rewards exact terms.

Each source has an “included in ingest” toggle. Only included sources are indexed. Excluding a source removes it from retrieval without deleting it.

Semantic vs Keyword

Keyword search looks for the exact words you typed, the way you would search a PDF for a medication name or a phrase from a guideline. It is precise when the source uses the same wording as your query, but it can miss clinically equivalent language.

Keyword search
Query: "heart failure treatment" Matches: Exact text "heart failure" AND "treatment" Misses: "HFrEF management", "cardiac dysfunction therapy"

Semantic search looks for meaning, so it still matches when the note uses one phrase and the guideline uses another.

Semantic search
Query: "heart failure treatment" Matches: Any semantically similar concepts - "HFrEF management" - "cardiac dysfunction therapy" - "GDMT for reduced EF"

Source Types

  1. Local: sources attached to the current note. Good for uploads tied to a specific patient.
  2. Account: guidelines, protocols, and papers in your persistent library. Indexed once and available to every note on your account.
  3. PubMed: queried live rather than indexed ahead of time. The agent rewrites your request into a PubMed search string, and the abstracts that come back are embedded into the same retrieval pool as your local and account sources.

Citations can come from any of the three.

When a question needs current information that is not in your library or PubMed, the assistant can run a live web search through Tavily, which returns compact results with citations built for use by AI agents.

Without a Tavily key the assistant skips web search and falls back to your other sources. As with every other source, results the assistant uses are cited in the response.

Retrieval Pipeline

Retrieval has two stages: ingest and search. Before an agent generates a response, supporting passages are pulled from your library and the literature and sent along with the question.

Ingest

1. Add a Source

Sources come from uploads, pasted text, account sync, or PubMed search, and each one is normalized into a RagSource record.

2. Extract Text When Needed

PDF uploads are parsed in the browser with pdfjs-dist. If no usable text can be extracted, the source is still stored but excluded from normal text ingest.

3. Preserve Scope

Each source is scoped local or account. Account sources can be merged into the current folder when the user is signed in.

Search runs in this order:

  1. collect eligible text
  2. embed sources
  3. embed the query
  4. score matches
  5. return top sources
  6. build the cited prompt

Retrieval Defaults

The semantic search flow:

  • chunks source documents into passages
  • embeds text content with text-embedding-004
  • uses cosine similarity for matching
  • returns a small result set
  • builds the prompt with [Source N] labels

Retrieval Controls

  • Source filters: limit retrieval to a specific library, such as local, account, or public sources.
  • Date windows: limit to a time range, for example guidelines from the last five years.
  • Relevance thresholds: drop chunks below a minimum similarity score to keep weak matches out of the context.

PubMed Path

PubMed follows a parallel path:

  1. search PubMed
  2. load summaries and abstracts
  3. convert articles into RAG sources
  4. add the selected articles to the active context

Citations

When retrieval returns supporting passages, each one is attached to the response as a citation.

How Citations Appear

Citations show up in two places: inline markers on the claims they support, and a source list under the response.

Cited response
Start broad-spectrum coverage within 1 hour of recognition [Source 1]. Piperacillin-tazobactam 4.5 g IV q6h is first line; add vancomycin if MRSA risk factors are present [Source 1]. 📚 Sources [1] Sepsis Guidelines 2024

Markers are numbered in the order sources were retrieved. The numbering restarts with every response: [Source 1] in one answer and [Source 1] in the next can be different documents.

When Citations Are Missing

An answer without expected citations usually means:

  1. The source is empty, or no usable text could be extracted from it.
  2. The source’s “included in ingest” toggle is off.
  3. The question didn’t match any passage in your library.

API Reference

The REST API covers documents, templates, tool requests, and export workflows.

Base URL

Base URL
https://philipshih.org/apps/rosetta/api/v1

Authentication

Bearer token in the Authorization header:

Authenticated request
Authorization: Bearer YOUR_API_KEY

Rate Limiting

TierLimitBurst
Free100 requests/hour10 concurrent
Professional1,000 requests/hour50 concurrent
Enterprise10,000 requests/hour100 concurrent

Documents API

List Documents

GET /v1/documents

Query Parameters

ParameterTypeDescription
limitintegerResults per page (default: 20, max: 100)
offsetintegerPagination offset (default: 0)
sortstringcreated_at, updated_at, title
orderstringasc or desc
searchstringQuery matched against the full text of documents
tagstringFilter by tag

Example

List documents
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://philipshih.org/apps/rosetta/api/v1/documents?limit=10&sort=created_at&order=desc"

Get Document

GET /v1/documents/:id

Create Document

POST /v1/documents

Request Body

Create document
{ "title": "New Progress Note", "content": "Document content in markdown...", "tags": ["progress-note"], "template_id": "tpl_123", "metadata": { "document_type": "progress_note", "date_of_service": "2026-01-20" } }

Update Document

PATCH /v1/documents/:id

Delete Document

DELETE /v1/documents/:id

Delete Response

Delete response
{ "success": true, "message": "Document moved to trash", "recoverable_until": "2026-02-19T09:15:00Z" }

Templates & Tools

Templates

List Templates

GET /v1/templates

Get Template

GET /v1/templates/:id

Tool Requests

Ask Question

POST /v1/ai/ask

Request Body

Ask request
{ "question": "What is the treatment for community-acquired pneumonia?", "context": "Outpatient, no comorbidities", "include_citations": true }

Generate Text

POST /v1/ai/generate

Request Body

Generate request
{ "prompt": "Expand on COPD exacerbation", "context": "Progress note, moderate severity", "max_tokens": 500, "temperature": 0.7 }

Export & Webhooks

Export Document

POST /v1/documents/:id/export

Request Body

Export request
{ "format": "pdf", "options": { "include_metadata": true, "include_citations": true, "page_size": "letter", "orientation": "portrait" } }

Supported Formats

  • pdf
  • docx
  • html
  • txt
  • markdown

Webhooks

Subscribe to account events from Settings -> Webhooks.

Available Events

  • document.created
  • document.updated
  • document.deleted
  • export.completed
  • ai.query_completed

Payload Example

Webhook payload
{ "event": "document.created", "timestamp": "2026-01-20T10:30:00Z", "data": { "document_id": "doc_abc123", "title": "New Progress Note" } }

Errors & SDKs

Error Response Format

Error response
{ "error": { "type": "validation_error", "message": "Invalid request parameters", "details": [ { "field": "title", "issue": "Title is required" } ], "request_id": "req_abc123" } }

HTTP Status Codes

CodeMeaningDescription
200OKSuccessful request
201CreatedResource created successfully
400Bad RequestInvalid request parameters
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions
404Not FoundResource does not exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorContact support

Error Types

  • authentication_error
  • validation_error
  • permission_error
  • rate_limit_error
  • not_found_error
  • server_error

SDKs

Python

Install (pip)
pip install rosetta-client
List documents
from rosetta import RosettaClient client = RosettaClient(api_key="YOUR_API_KEY") documents = client.documents.list(limit=10)

JavaScript / TypeScript

Install (npm)
npm install @rosetta/client
List documents
import { RosettaClient } from "@rosetta/client"; const client = new RosettaClient({ apiKey: "YOUR_API_KEY" }); const documents = await client.documents.list({ limit: 10 });