Backend reference
The backend is packages/backend-core (src/server.js and the modules next to it), started by apps/backend for local use and by apps/desktop inside Electron. It is a Node HTTP server with a WebSocket endpoint at /ws. It stores projects and sessions on disk, runs Genie tools, and bridges each chat to an OpenAI Codex CLI app-server process.
Start it with pnpm dev:backend (port 8787 on 127.0.0.1 by default). Configuration variables are listed in Configuration.
The backend has no authentication and sends Access-Control-Allow-Origin: *. It is meant to be bound to 127.0.0.1 on a single-user machine.
HTTP routes
All JSON routes answer with application/json. Project, session and asset ids are limited to letters, digits, _ and -.
| Route | Methods | Purpose |
|---|---|---|
/health | GET | { "ok": true } |
/genomes | GET | defaultGenomeId and genome summaries from the catalog |
/projects | GET, POST | List projects (with recent chats); create a project |
/projects/:projectId | GET, PATCH, DELETE | Read, update (for example the title) or delete a project |
/projects/:projectId/assets | GET, PUT | Read or replace the project's asset catalog |
/projects/:projectId/assets/import | POST | Upload or import assets (uploads up to 100 MB) |
/projects/:projectId/assets/:assetId/file | GET | Download an asset's file |
/projects/:projectId/analysis | GET, PATCH | Analysis-mode state |
/projects/:projectId/analysis/artifact | GET | The current analysis artifact (index.html) |
/projects/:projectId/studio | GET, POST | List or create Studio outputs |
/projects/:projectId/studio/:outputId | GET, DELETE | Read or delete a Studio output |
/projects/:projectId/studio/:outputId/file | GET | A Studio output's HTML file |
/projects/:projectId/genome | GET, PATCH | The project's browser state (ProjectGenomeState) |
/projects/:projectId/genome/results/:file | GET, HEAD | A result file written by a tool (BED, TSV) |
/projects/:projectId/genome/plugins/:pluginId | GET | A compiled custom-track plugin bundle (custom tracks are currently disabled) |
/sessions | GET | All chat sessions, newest first |
/sessions/:sessionId | PATCH, DELETE | Rename ({ "title": ... }) or delete a chat |
/sessions/:sessionId/genome | GET, PATCH | Browser state for a chat without a project (older layout) |
/sessions/:sessionId/genome/plugins/:pluginId | GET | Plugin bundle for a chat without a project |
/sessions/:sessionId/artifacts/:artifactId/index.html | GET | An HTML artifact made by create_html_artifact in Genome mode |
/sessions/:sessionId/images/:file | GET | An image pasted into the chat |
/models | GET | Models from the Codex CLI (model/list), cached for 5 minutes |
/asset-catalog | GET, PUT | Workspace-level asset catalog (older layout) |
When GENIE_STATIC_ROOT is set, any other GET or HEAD request is served from that folder, and HTML requests for app routes (such as /projects/<id>) get the frontend's index.html. HTML files are sent with Cache-Control: no-store; other static files are cached for a year.
GET /models
The backend starts a short-lived app-server, sends initialize and model/list, then stops it. The result is cached for 5 minutes. If the call fails and nothing is cached, the route returns a fallback list (gpt-5.5 as the default, gpt-5.4, gpt-5.4-mini).
PATCH /projects/:projectId/genome
The body may contain tracks (array), hiddenTrackIds (array, filtered to existing track ids), plugins (array) and viewRegion (locus, start, end). genomeId may only repeat the project's genome; any other value is rejected with "Project genome cannot be changed. Create a new project with the desired genome."
WebSocket protocol
Connect to ws://<host>:<port>/ws. Every message is a JSON object with a type and usually a payload.
Client to server
| Type | Payload | Effect |
|---|---|---|
init | See below | Starts the app-server handshake. Sent once per connection. |
send | { text, images?, model?, effort? } | Starts a turn. model and effort override the session's settings from this turn on. |
abort | None | Sends turn/interrupt for the current turn. |
tool_result | { requestId, callId?, output, success } | Answers a tool_call_request that the frontend handled. |
tool_input_response | { requestId, answers } | Answers a tool_input_request. |
approval_response | { requestId, decision } | Answers an approval_request. |
The init payload sent by the frontend:
{
"type": "init",
"payload": {
"sessionId": "<chat id>",
"projectId": "<project id>",
"sessionMode": "regular",
"model": "<model id or null>",
"effort": "<effort or null>",
"tools": [{ "name": "genome_navigate", "description": "...", "inputSchema": {} }],
"developerInstructions": "<mode instructions>",
"autoApprove": true
}
}
sessionMode is regular (Genome mode), analysis (plan is accepted as an alias) or asset-import. tools are the mode's declared tool schemas; the backend passes them to the app-server as dynamic tools. The backend appends its own instruction to the base instructions: work only inside the session's directory when creating or modifying files.
Server to client
| Type | Payload |
|---|---|
connected | { sessionId }, sent as soon as the socket opens |
ready | { sessionId }, sent after the handshake (and thread resume, if any) |
event | A chat event, see below |
tool_call_request | { requestId, callId, tool, arguments, threadId, turnId } for tools the backend does not handle |
approval_request | { requestId, kind, params } where kind is command, file, patch or exec; only sent when autoApprove is off |
error | { message } |
Event payload types: thread_started, message_started, message_delta, message_completed, tool_call, tool_progress, tool_result, tool_input_request, tool_input_resolved, status (thinking or idle) and error.
The frontend's WebSocketChatTransport waits up to 30 seconds for ready. If it does not arrive, the chat shows "The assistant runtime did not finish initializing within 30 seconds."
CodexSession lifecycle
Each WebSocket connection creates one CodexSession, and each CodexSession creates one CodexAppServerClient, which spawns <GENIE_CODEX_COMMAND> app-server with piped standard input and output. Messages are newline-delimited JSON-RPC.
| Step | JSON-RPC | Details |
|---|---|---|
| Handshake | initialize, then notification initialized | clientInfo { name: "genie", title: "Genie Chat" }, capabilities.experimentalApi: true |
| Resume | thread/resume, then thread/read | Only if sessions.json holds a thread id for this chat. Replayed messages get their stored citations reattached. |
| First message | thread/start | model, cwd (the session folder), approvalPolicy, sandbox, baseInstructions, developerInstructions, dynamicTools. approvalPolicy and sandbox are sent as null unless set; config is always null. |
| Each message | turn/start | input (text and images), model, effort, and sandboxPolicy { type: "workspaceWrite", networkAccess: true, writableRoots: [session folder] } |
| Stop | turn/interrupt | From the abort message |
| Models | model/list | Separate short-lived client for GET /models |
Notifications the backend translates into events include thread/started, turn/started, turn/completed, item/started, item/completed, item/agentMessage/delta, item/commandExecution/outputDelta, item/fileChange/outputDelta, item/mcpToolCall/progress and error. These cover the model's messages and the runtime's built-in items (shell commands, file changes, MCP tool calls), which appear in the chat as tool cards.
Approvals
Server requests item/commandExecution/requestApproval, item/fileChange/requestApproval, applyPatchApproval and execCommandApproval are answered with accept when autoApprove is true. The frontend always sends autoApprove: true, so no approval request reaches the user. Genie does not turn off the runtime's built-in shell or web search. See Runtime and safety.
Tool execution
The app-server sends item/tool/call with callId, tool and arguments. CodexSession.handleServerRequest handles it:
- Sends a
tool_callevent (statusstarted) to the frontend and logstool_call_received. - Dispatches by tool name. Most genome tools (
genome_navigate,genome_add_track,genome_describe_viewport,genome_signal_stats,genome_call_peaks,genome_quantify_at_features,genome_correlate_tracks) andpublic_data_searchgo throughrunGenomeToolForProject, which reads the project's browser state (after pulling it from the cloud project API ifGENIE_CLOUD_API_URLis set), runs the handler insrc/genome/toolHandlers.js, and adds citations. The other genome tools and the asset, analysis and artifact tools have their own handlers inserver.js. - Writes any new citation records to the chat's
citations.json. - For
genome_navigate,genome_add_track,genome_call_peaksandgenome_quantify_at_features, pushes the new browser state to the cloud project API when one is configured. - Answers the app-server with
{ contentItems: [{ type: "inputText", text: <JSON> }], isError }(plus legacyoutputandsuccessfields) and sends atool_resultevent to the frontend. - Logs
tool_call_completedwith the elapsed time.
A thrown error becomes a failed tool result whose text is the error message. Unknown names starting with genome_, asset_catalog_ or project_ fail with "Unsupported … tool". Any other unknown name is forwarded to the frontend as a tool_call_request.
The tools themselves are described in the Tool reference.
On-disk layout
The session data root is genie/session-data/ by default (GENIE_SESSION_DATA_ROOT), or session-data/ in Electron's user-data folder for the desktop app.
session-data/
projects.json project list
sessions.json chat list (title, preview, mode, thread id, project id)
assets/asset-catalog.json workspace-level catalog (older layout)
projects/
<projectId>/
assets/
asset-catalog.json project asset catalog
<assetId>/
metadata.json
artifacts/ files written by project_write_asset_artifact
analysis/
state.json Analysis-mode state
artifact/index.html
studio/<outputId>/index.html Studio outputs (reports, tables, saved views, ...)
genome/
browser-state.json ProjectGenomeState
results/ peaks-*.bed, quantify-*.tsv
plugins/ custom-track plugin sources and bundles (disabled)
sessions/
<sessionId>/
app-server.log JSON lines: every app-server message and tool event
citations.json citation records for this chat
artifacts/<artifactId>/index.html
input-images/ images pasted into the chat
Chats created without a project use session-data/<sessionId>/ with the same per-chat files.
app-server.log
One JSON object per line with timestamp, sessionId, projectId, direction (outbound, inbound or internal), channel (app-server or backend) and either the JSON-RPC message or a backend event (tool_call_received, tool_call_completed with elapsedMs). The log records every request and response exchanged with the app-server, so it is the primary record of a session.
The log contains the full conversation, tool arguments and results, and runtime details such as local file paths. Review and redact it before sharing.
citations.json
{
"schemaVersion": 1,
"updatedAt": 1767225600000,
"citations": [
{
"citationId": "cite_...",
"citationToken": "[[cite:cite_...]]",
"sourceType": "genome-computation",
"projectId": "<projectId>",
"title": "Genome call peaks result",
"excerpt": "{ ...result JSON, up to 1,800 characters... }",
"genome": { "genomeId": "hg38", "locus": "chr8:127700000-127760000", "start": 127700000, "end": 127760000 },
"createdAt": 1767225600000
}
]
}
Other fields a record may carry: assetId, url, browserUrl, location, fileName, lineStart, lineEnd, genome.trackId and metadata. The file is rewritten atomically (temporary file, then rename). See Citations and evidence.
browser-state.json
The project's ProjectGenomeState, served by GET /projects/:projectId/genome and changed by PATCH and by the mutating genome tools.
| Field | Type | Notes |
|---|---|---|
schemaVersion | number | 1 |
genomeId | string | Fixed when the project is created |
viewRegion | { locus, start, end } | locus is a display string such as chr8:127,700,000-127,760,000 |
tracks | TrackModel[] | id, type, name, url, optional label, heightPx, options, metadata, indexUrl, ... |
hiddenTrackIds | string[] | Optional; tracks hidden in the panel |
plugins | array | Custom-track plugin records (empty while custom tracks are disabled) |
updatedAt | number | Milliseconds since the epoch |
A new project's state is created from the genome catalog: the default region and default tracks of its genome. The file is written atomically.
Result files
genome_call_peaks writes genome/results/peaks-<trackId>-<8 characters>.bed, a tab-separated file with chr, start, end, peak_<n> and score, and adds it to the browser state as a bed track whose url is the backend-relative path /projects/<projectId>/genome/results/<file>. The track's metadata records the parameters (trackId, scope, method, threshold, minWidth) and the source track ids.
genome_quantify_at_features writes genome/results/quantify-<signalTrackId>-at-<featureTrackId>-<8 characters>.tsv with a header row:
rank chr start end name featureScore mean
The last column is named after the chosen metric (mean, sum or max). The quantify tool does not add a track.
In earlier testing, the BED tracks added by the peak tool did not draw in the eg3 panel ("Error detecting chromosome naming. Check URL and file format."). The files themselves are complete and can be downloaded from the results route.