Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/aipoch/open-science/llms.txt

Use this file to discover all available pages before exploring further.

Artifacts are any files produced during a research session — plots, datasets, notebooks, reports, XML exports, SVG figures, HTML summaries, CSV tables, or any other output the agent or notebook kernel writes. Open Science stores them durably on disk, organized by project, session, and message, so that results are always reproducible and every output can be traced back to the turn that produced it.

The ArtifactFile Shape

Every artifact is described by an ArtifactFile record (defined in src/shared/artifacts.ts). This type crosses the IPC boundary and is used by both the runtime and the renderer.
FieldTypeDescription
idstringUnique artifact ID, composed as sessionId:ownerId:filename.
projectNamestringThe logical project bucket that owns this artifact’s storage subtree.
sessionIdstringThe session that produced this artifact.
messageIdstring?The message ID assigned after finalization; absent while the run is still pending.
runIdstring?The run ID assigned at prompt activation; present for pending artifacts.
namestringThe display filename (e.g. histogram.png).
pathstringAbsolute path on disk, used by the main process for open/preview operations.
fileUrlstringfile:// URL generated by pathToFileURL(), used by the renderer to load the file.
mimeTypestring?MIME type if supplied by the agent or inferred; absent for formats without registered types.
sizenumberFile size in bytes, from fs.stat.
mtimeMsnumberLast-modified time as epoch milliseconds, from fs.stat.

Artifact Lifecycle

1

Agent calls write_artifact_file

During a prompt turn, the agent calls the write_artifact_file tool exposed by the open-science-artifacts MCP server. The call specifies a filename, an optional MIME type, and either an inline source or a local file path. The MCP server validates the filename (no path separators, no control characters, no .. traversal) and delegates to ArtifactRepository.writePendingFile(), which writes the content to:
artifacts/<projectName>/<sessionId>/.pending/<runId>/<filename>
The write is atomic — content is written to a .tmp file first, then renamed to the final path.
2

activateArtifactRun() writes the handoff file

Before sendPrompt() calls the agent, activateArtifactRun() writes a small JSON file at:
artifacts/<projectName>/<artifactSessionId>/.pending/current-run.json
This file contains { "runId": "artifact-run-<timestamp>-<seq>" }. The artifact MCP server reads this file to discover the active run ID without the agent ever supplying it directly, preventing model-supplied IDs from influencing storage paths.
3

emitArtifactRunEvent() claims pending files

When the agent’s response stream emits a stop signal, emitArtifactRunEvent() calls ArtifactRepository.listPendingRunFiles() to discover all files written during the run. If any files were written, the ArtifactRunRegistry registers a claim and returns an opaque artifactClaimId. The runtime then emits an artifact event carrying the claim ID and the ArtifactFile list so the renderer can attach them visually to the finishing message — before the stop event fires.
4

finalizeRun() commits artifacts to a message

After the renderer assigns a messageId to the finished assistant message, it calls artifacts:finalizeRun via IPC with the claimId and messageId. The main process resolves the claim through ArtifactRunRegistry.resolve(), then calls ArtifactRepository.finalizeRunArtifacts() to move every file from the pending directory to:
artifacts/<projectName>/<sessionId>/<messageId>/<filename>
The finalizeRunArtifacts() method is idempotent — a replayed finalization recovers metadata from already-moved files and returns the correct list without duplicating writes.

Write Sources

The agent passes one of two ArtifactWriteSource variants to write_artifact_file:
export type ArtifactWriteSource =
  | {
      kind: 'inline'
      content: string
      encoding: 'utf8' | 'base64'   // base64 for binary content (images, PDFs, etc.)
    }
  | {
      kind: 'localPath'
      path: string                  // path to a file already on disk (e.g. written by a notebook run)
    }
For localPath writes, the ArtifactRepository resolves the path against a set of allowed import roots configured at session creation — the session working directory and the notebook session root. This prevents the agent from importing arbitrary files from outside its managed workspace.
A localPath source that resolves outside the allowed import roots causes writePendingFile() to throw: "Artifact local source path is outside allowed artifact import roots." The agent must write files to the workspace or notebook directory first before they can be claimed as artifacts.

Storage Root

All app-managed data — the SQLite database, session JSON files, artifact files, and notebook run history — lives under a single root directory resolved by src/main/storage-root.ts:
PlatformProduction pathDevelopment path
macOS~/.open-science~/.open-science-project
Windows%USERPROFILE%\.open-science%USERPROFILE%\.open-science-project
Linux~/.open-science~/.open-science-project
Development builds use the isolated .open-science-project directory so that running npm run dev never touches a production installation’s data. The dev/prod branch is determined by app.isPackaged at runtime. Artifacts specifically live under:
<storageRoot>/artifacts/<projectName>/<sessionId>/
The DEFAULT_ARTIFACT_PROJECT_NAME constant ('default-project') is used as the projectName for any session that is not explicitly associated with a named project. This ensures the artifact storage layout is always valid even before the researcher creates their first project.

In-App Preview

The preview workbench renders artifact files natively without requiring the researcher to open them in an external tool. Supported formats include CSV, FASTA, HTML, image formats (PNG, JPG, SVG, and others), JSON, Markdown, and plain text, plus a read-only notebook view that shows code cells and their execution output side by side. Renderer-initiated preview reads go through artifacts:readPreview IPC, which calls ArtifactRepository.readManagedFilePreview(). This method validates that the requested path is genuinely inside the artifact storage root (rejecting path traversal and symlink escapes) before reading the file, and returns a bounded text preview with a truncated flag for large files.

Build docs developers (and LLMs) love