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:
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
- 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.
- Open a note and apply a template from the template menu if you want a fixed structure.
- Write. Autosave runs continuously, and the timestamp in the tab strip shows the last save.
- Add reference material in the RAG panel to let answers cite your own sources.
- Ask the assistant in chat, or select text and right-click to run an agent on just that selection.
- 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:
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
| Shortcut | Action |
|---|---|
Ctrl/Cmd + S | Save the current note, or the template in the template editor |
Ctrl/Cmd + W | Close the current note tab |
Esc | Close the settings panel |
/ | Slash commands in the editor |
Smart phrases expand as you type. For example:
.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:
55yo M c CP, SOB x2dproduces a pending suggestion like:
55-year-old male with chest pain and shortness of breath for two daysRAG Workflow
Add a source in the RAG panel first. Click Add Source, give it a name, and paste or upload content:
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 factorsEach source has an “included in ingest” toggle, and only included sources are searched.
Then ask the assistant a clinical question in chat:
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
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
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
[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:
cd rosetta
npm install
npm run devThe 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
| Area | What it does |
|---|---|
EditorCore | Mounts Lexical, tracks selection, and dispatches commands. |
LexicalNoteEditor | The 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. |
TemplateManager | Holds template anchors and fields. Blocks writes to locked regions. |
SuggestionOverlay | Renders pending agent diffs in the editor and routes review to the revisions surface. |
SyncBridge | Persists debounced plain text and Lexical JSON snapshots, with blur and unload flushes. |
Core Rules
- All document mutations go through
editor.update(). Every write is transactional and observable. - Lexical is the live typing source of truth. React note state and persisted JSON are snapshots.
- Agents and automation cannot modify locked template anchors.
- 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.
- Undo/redo only covers changes that actually landed. Rejected and stale suggestions never wrote to the document.
- 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:
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
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
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:
- Nodes: compare editor subtrees by identity. If a whole paragraph or template block did not change, the pass skips it.
- Tokens: compare sentence sized chunks or template chunks. Most clinical edits stop here because this is accurate enough for review.
- Characters: compare individual characters when token precision drops below 98%. This lets the overlay highlight the exact text that changed.
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:
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
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
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
| Type | Color | Behavior |
|---|---|---|
| Insert | Green | Preview as additive text |
| Replace | Yellow | Original highlighted with replacement preview |
| Delete | Red | Original shown with remove indicator |
Template Anchors
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: medication | aspirin, clopidogrel, warfarin}}MULTISELECT multiple values:
{{MULTISELECT: symptoms | chest pain, dyspnea, diaphoresis}}DYNAMIC_SELECT options resolved from query:
{{DYNAMIC_SELECT: drug | query: medications for hypertension}}Field Node Rendering
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:
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
<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.
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
| Key | Action |
|---|---|
Ctrl/Cmd + S | Save the current note, or the template when the template editor is open |
Ctrl/Cmd + W | Close the current note tab, or the template editor |
Esc | Close the settings panel |
Editing
| Key | Action |
|---|---|
Ctrl/Cmd + Z | Undo |
Ctrl/Cmd + Shift + Z | Redo |
Tab | Indent the current block, or each block when several are selected |
Shift + Tab | Outdent |
/ | Open slash commands |
Smart Phrases
| Key | Action |
|---|---|
Enter or Tab | Accept the suggested expansion |
Esc | Dismiss 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
- open the note
- apply the template
- draft or edit
- run chat or a workflow
- review the suggestion
- 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:
- open the folder
- collect notes
- run the presentation workflow
- 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.
| Agent | What it does |
|---|---|
Shorthander | Expands medical abbreviations (SOB becomes shortness of breath). Skips locked anchors. |
Ingester | Parses uploaded PDFs and text files into searchable chunks. Keeps the original metadata for tracing any chunk back to its source. |
Reasoner | Generates ranked differentials and plans organized by problem. Weighs claims that have retrieved evidence over ones that don’t. |
Reformatter | Rewrites selected text to match a requested format (SOAP, bullet list, table). The meaning stays the same. |
Citations | Searches PubMed and attaches references to the relevant claims. |
Clinical Tools
During a chat turn the assistant can also call clinical tools directly:
| Tool family | Coverage |
|---|---|
| Terminology lookups | ICD-10, LOINC, and RxNorm |
| Literature and regulatory data | PubMed, OpenFDA, and clinical trial registries |
| Scores and calculations | Common scoring systems and clinical math, computed in code rather than by the model |
For example, asking:
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:
| Trigger | Expansion |
|---|---|
.cc | Chief Complaint section |
.hpi | History of Present Illness |
.ros | Review of Systems |
.pe | Physical Examination |
.ap | Assessment & Plan |
.dc | Discharge 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
| Source | What it is |
|---|---|
| built in templates | Templates shipped with the app |
| user templates | Templates saved in versioned local storage |
| workspace template | Default template selected for the current workspace session |
| local note template | A local override of the template anchor set |
Pipeline Steps
- Select template: load the structure and its anchor and field definitions.
- Bind field values: resolve
SELECT,MULTISELECT, andDYNAMIC_SELECTinputs and insert them at their anchors. - Run agent transforms: agents propose edits against the bound document.
- Validate locked regions: any proposed change that overlaps a locked anchor is dropped before it reaches the review queue.
- Queue pending edits: remaining suggestions wait for the user to accept or reject them.
Global vs Local Template Modes
| Mode | What it is |
|---|---|
global | The note follows a shared template definition |
local | The 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:
| Format | What it is |
|---|---|
natural | Template content with readable headings |
smartphrase | SmartPhrase 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.
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.
Query: "heart failure treatment"
Matches: Any semantically similar concepts
- "HFrEF management"
- "cardiac dysfunction therapy"
- "GDMT for reduced EF"Source Types
- Local: sources attached to the current note. Good for uploads tied to a specific patient.
- Account: guidelines, protocols, and papers in your persistent library. Indexed once and available to every note on your account.
- 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.
Web Search
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
Search runs in this order:
- collect eligible text
- embed sources
- embed the query
- score matches
- return top sources
- 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:
- search PubMed
- load summaries and abstracts
- convert articles into RAG sources
- 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.
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 2024Markers 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:
- The source is empty, or no usable text could be extracted from it.
- The source’s “included in ingest” toggle is off.
- 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
https://philipshih.org/apps/rosetta/api/v1Authentication
Bearer token in the Authorization header:
Authorization: Bearer YOUR_API_KEYRate Limiting
| Tier | Limit | Burst |
|---|---|---|
| Free | 100 requests/hour | 10 concurrent |
| Professional | 1,000 requests/hour | 50 concurrent |
| Enterprise | 10,000 requests/hour | 100 concurrent |
Documents API
List Documents
GET /v1/documentsQuery Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page (default: 20, max: 100) |
offset | integer | Pagination offset (default: 0) |
sort | string | created_at, updated_at, title |
order | string | asc or desc |
search | string | Query matched against the full text of documents |
tag | string | Filter by tag |
Example
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/:idCreate Document
POST /v1/documentsRequest Body
{
"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/:idDelete Document
DELETE /v1/documents/:idDelete Response
{
"success": true,
"message": "Document moved to trash",
"recoverable_until": "2026-02-19T09:15:00Z"
}Templates & Tools
Templates
List Templates
GET /v1/templatesGet Template
GET /v1/templates/:idTool Requests
Ask Question
POST /v1/ai/askRequest Body
{
"question": "What is the treatment for community-acquired pneumonia?",
"context": "Outpatient, no comorbidities",
"include_citations": true
}Generate Text
POST /v1/ai/generateRequest Body
{
"prompt": "Expand on COPD exacerbation",
"context": "Progress note, moderate severity",
"max_tokens": 500,
"temperature": 0.7
}Export & Webhooks
Export Document
POST /v1/documents/:id/exportRequest Body
{
"format": "pdf",
"options": {
"include_metadata": true,
"include_citations": true,
"page_size": "letter",
"orientation": "portrait"
}
}Supported Formats
pdfdocxhtmltxtmarkdown
Webhooks
Subscribe to account events from Settings -> Webhooks.
Available Events
document.createddocument.updateddocument.deletedexport.completedai.query_completed
Payload Example
{
"event": "document.created",
"timestamp": "2026-01-20T10:30:00Z",
"data": {
"document_id": "doc_abc123",
"title": "New Progress Note"
}
}Errors & SDKs
Error Response Format
{
"error": {
"type": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "title",
"issue": "Title is required"
}
],
"request_id": "req_abc123"
}
}HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource does not exist |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Contact support |
Error Types
authentication_errorvalidation_errorpermission_errorrate_limit_errornot_found_errorserver_error
SDKs
Python
pip install rosetta-clientfrom rosetta import RosettaClient
client = RosettaClient(api_key="YOUR_API_KEY")
documents = client.documents.list(limit=10)JavaScript / TypeScript
npm install @rosetta/clientimport { RosettaClient } from "@rosetta/client";
const client = new RosettaClient({ apiKey: "YOUR_API_KEY" });
const documents = await client.documents.list({ limit: 10 });