The document format

A vibe-sketcher document is one JSON object that holds a drawing and everything the compute side needs from it. It is saved as QVF, the vibe family’s file format, or as plain JSON in a .vsk file. This page describes schema version 1, identified by "schema": "vibe-sketch/1".

In a QVF file

A vibe-sketcher QVF uses archive format version 1. The optional qvf-writer toolkit has its own release numbering; see installation for supported toolkit versions. The archive holds:

Section

Kind

Contents

vibesketch_document

x_vibesketch.document

document: this JSON object. species: each species’ id, compound number, name, SMILES, InChI, InChIKey, formula, charge, multiplicity and its structure section, those without a 3D structure included. sdf: the species as an SD file.

preview

preview

thumbnail: a PNG of the species, 256 to 1024 pixels on its longest side, the picture a file manager shows. image: the scheme as SVG.

structure_<species id>

structure

structure: the species’ first conformer, atoms in angstrom; bonds: its bonds with orders. On the section: the species’ charge, multiplicity, SMILES, InChI, InChIKey, its name when you gave it, and its compound number as label.

reaction_scheme

reaction.scheme

scheme: the reaction steps whose reactants and products all have a 3D structure, each species named by its structure section.

job_<job id>

job.spec

spec: a calculation on a species with a 3D structure, one section per job.

The embedded document member is always present. The species index is added when species exist, and SDF is added when it can be exported. Preview generation is optional and can be omitted when rendering is unavailable or the document has nothing renderable. A missing preview does not mean the sketch is lost.

Standalone structure sections contain only the first conformer of species that have 3D geometry. Reaction and job sections are emitted only when their required structures and supported data are available. All saved conformers, drawing data and job records remain in the embedded native document.

The x_vibesketch namespace is declared in the manifest’s extensions, version 1, not critical: a QVF reader that does not know vibe-sketcher can skip the native document and read the standard sections that are present. A 2D-only sketch need not contain standalone structures usable by a 3D viewer. source.program is vibe-sketch, and the media type is application/vnd.vibe-qc.qvf+zip.

The scheme records each species by its chemical role, as QVF does: a retrosynthetic arrow points from the target to its precursors, so the species drawn at its tail are the step’s products. A reagent without a 3D structure is named in the step’s conditions. A yield printed as 87% is also given as a number.

The calculations are a QVF job set. Each job names its structure and states the species’ charge and multiplicity, the method and functional in vibe-qc’s names (rks with b3lyp, uhf, ccsd(t), r2scan-3c), the basis, dispersion, solvation and resources. What only some programs can do goes into the job’s options: aux_basis, approximation (such as rijcosx), states for TDDFT, constraints. A runner that does not know an option refuses the job rather than drop it, so a job fails closed as the input writers do. A geometry job computes nothing and has no section.

The machine-readable contract is the JSON schema in schema/vibe-sketch-1.schema.json (draft 2020-12), which vibe-sketcher schema prints and the server serves at GET /api/schema/document. It is generated from the Python model and a test fails when the two drift.

Two layers

The canvas is the drawing exactly as the canvas engine stores it. Today that is KET, Ketcher’s JSON format, embedded verbatim: whatever the engine writes comes back unchanged, including fields vibe-sketcher does not know about. That is what makes a round trip through the browser lossless.

The normalized layer is what the compute side reads: species with their identifiers, charge, multiplicity and 3D conformers; reactions with roles, arrows and conditions; program-neutral job specifications; the figure style; and provenance. It is derived from the canvas by the server and can be written directly through the API by an agent.

Species identity and editing consequences

A species record represents a distinct chemical identity, not each occurrence of a molecule on the canvas. Repeated drawings with the same identity share one species ID and one set of labels, conformers and jobs. For example, importing CCO.CCO draws two ethanol fragments but produces one ethanol species record. Reaction roles also contain unique species IDs; repeated fragments are not stored as stoichiometric coefficients.

On a canvas edit, derivation matches existing species by InChIKey, with a SMILES fallback when no InChIKey is available. A matching species keeps its ID and user metadata; conformers are retained when charge and multiplicity also match. Moving a molecule is therefore different from changing its chemical identity. Read the returned species list after chemical edits rather than continuing to use an ID from an earlier snapshot.

If an edit removes a species, jobs referring to it are removed from that document, with a warning. Their run records disappear with those jobs; this does not cancel the external queue runs or delete their result files. Save a copy before changing a sketch whose calculation history you want to retain. The calculation-run reference explains which information lives in the document and which lives in the queue.

A minimal document

{
  "schema": "vibe-sketch/1",
  "provenance": {"app": "vibe-sketch 0.1.0", "created": "2026-10-13T00:00:00Z"}
}

Every other field has a default: revision 0, an empty KET canvas, no species, reactions or jobs, and the acs-1996 style.

Fields

Field

Type

Meaning

schema

"vibe-sketch/1"

The format and its major version.

revision

integer, at least 0

Incremented by the server on every accepted edit; an edit names the revision it read.

canvas

object

format ("ket") and data, the KET document or {}.

species

list

The molecules and ions in the drawing.

reactions

list

The steps of the scheme.

jobs

list

Calculations to run.

recipes

list

Saved staged workflows over species; absent when there are none.

highlights

list

Atoms marked in colour for the canvas and figures; absent when there are none.

generics

list

R-group cores and bracketed groups the drawing shows, derived from it; absent when there are none.

rgroups

list

The R-groups’ definitions, derived from the drawing; absent when there are none.

style

object

preset, a journal style such as acs-1996.

provenance

object

app, created (a timezone-aware timestamp) and edits.

A species has an id and optionally a label (its compound number in the scheme), smiles, inchikey, name, and conformers. charge is the sum of the formal charges (default 0). multiplicity is the spin multiplicity 2S+1 (default 1); it defaults from the radical count when the server derives it and is always editable, because a drawing cannot know the spin state of a metal centre. For gallium to bismuth the inert ns² pair is kept paired unless a radical is drawn: Pb²⁺, Sn²⁺, In⁺ and Bi³⁺ are singlets and a lead atom a triplet, where RDKit’s valence model alone would give Pb²⁺ two radicals. A name records its value, its source (for example vibeqc_naming or user) and a confidence of high, medium or low.

A species imported from a sequence or HELM also has a sequence: helm, its HELM as RDKit writes it (as given, for the few monomers RDKit reads but does not write), and inchikey, the InChIKey of the structure that HELM builds, which must be the species’ own. Deriving the species again keeps it only while the drawing is still that structure. It is absent for species drawn by hand.

A species made by capping a labelled structure has capped: the generic it came from, that structure’s cxsmiles, the cap (H or CH3) and the labels it replaced. Deriving species from the canvas keeps it while that structure is drawn.

A solid given by its formula (electrode reactions) has a solid record instead of smiles, inchikey and conformers: the text as drawn, the canonical formula, its key (with polymorph and state), polymorph, state, the composition (a count, or a variable count as written) and, when numeric, molar_mass_g_mol.

A generated nanostructure has a nanostructure record instead of smiles and inchikey: its parameters (with kind nanotube, graphene, nanoribbon or fullerene), identity (the canonical parameters), key (NANO- and 20 hexadecimal digits), a description, the formula in Hill order, bonds as [i, j, order] over the first conformer’s atoms (order 1, 1.5 or 2) and, for a periodic segment, periodic with its translation_angstrom and carbons_per_period. Its atoms are the first conformer, whose method is nanostructure. Deriving the species from the canvas keeps it, as it is not drawn there.

A generic structure is what the drawing shows that is not one species: an R-group core, a group in polymer brackets, or a structure bonded to a labelled attachment atom that stands for a material (CNT, Au; see materials at a bond end). It has an id, a kind (rgroup-core, bracketed or labelled, which also lists its labels), its cxsmiles without coordinates (R-group atoms labelled $_R1$, brackets as their Sg groups), optionally a label (its compound number) and, for a core, the rgroups it uses ([1, 2] for R1 and R2). An R-group definition has its number, its substituents as CXSMILES with the attachment points labelled _AP1, and the conditions drawn: occurrence (such as 1-3), rest_h and if_then (the R-group it requires). Both are derived from the drawing on every edit, keeping their ids and compound numbers, and are read-only: draw or import to change them. Only what figures can draw becomes an entry; the rest stays a note. See R-groups and polymer repeat units.

A conformer is one 3D structure: method (how it was made: etkdg+mmff94, etkdg+uff or etkdg from vibe-sketcher’s embedding, fire+mmff94 or fire+uff from its relaxation, atom for a lone atom, imported), symbols (one element per atom) and xyz_angstrom (one [x, y, z] per atom). Every conformer of a species has the same atoms in the same order, the drawing’s atoms first and then the hydrogens; writers use the first conformer. Generated conformers are ordered by their force-field energy when available; an imported geometry is not evidence of an energy minimum.

A reaction has an id, reactants and products (at least one each), reagents (species drawn at the arrow), an arrow (forward, equilibrium, retrosynthetic or resonance), conditions (text lines at the arrow), a yield as printed, such as "87%", and optional amounts: what the chemist entered for its stoichiometry table, one entry per participant with its species, a coefficient (default 1), and either equivalents or one of mmol, mass_mg and volume_ml, a volume with a density_g_ml or a concentration_mol_l. Only entered values are stored; molar masses, equivalents and yields are worked out when asked for. What a half-reaction’s drawing says is read back as well (electrode reactions): electrons (side and count), states (per participant s, l, g, aq or ads), coefficients (per participant, drawn before it) and quantities, the condition lines that read as a kind (potential, ph, temperature, time), a value, a unit, for a potential its reference electrode and an Ag/AgCl reference’s kcl_mol_l, and the text they came from. Each is absent when nothing was drawn.

A highlight (in the optional highlights) has a species, the atoms it marks as canonical ranks of the species’ heavy atoms (RDKit’s CanonicalRankAtoms with ties broken), and a colour from the palette in highlights. A document without highlights has no highlights field.

A job has an id, the species it computes, the program that writes its input (a writer name; see input writers) and a spec, the program-neutral specification:

spec field

Example

Meaning

task

opt+freq

geometry (no calculation), sp, opt, freq, opt+freq, tddft or nmr.

method

B3LYP

Required for every task but geometry.

basis, aux_basis

def2-TZVP, def2/J

Basis sets.

approximation

RIJCOSX

Integral approximation.

dispersion

D4

Dispersion correction.

solvent

{"model": "CPCM", "name": "water"}

Implicit solvation, CPCM or SMD.

states

10

Excited states; required for tddft, and only for it.

constraints

[{"kind": "distance", "atoms": [0, 1], "value": 1.21}]

Coordinates an optimisation holds fixed; opt and opt+freq only.

resources

{"nprocs": 8, "maxcore_mb": 3000}

Processes and memory per core.

A job may also carry runs, its submissions to a queue, oldest first: each has the queue (vibe-queue), the host (localhost), the queue’s id for the run, when it was submitted, the queue’s working directory for the run, where the program’s output is, for a vibe-qc QVF job container its file name, container, and the group of runs it was submitted with (a reaction’s or a scheme’s species under one specification, g1, g2, …), which is followed and cancelled as one; the finished container in that directory keeps the sketch and the result.

A recipe is a saved staged workflow over species of the document (recipes, optional and additive): an id, a name, the species it applies to and its stages, each run once per species. A stage has a stable id, a kind (prepare, mace, dft, hessian or dlpno), an optional parent (the stage whose scientific acceptance it waits for), a geometry naming where its coordinates come from, either a conformer of the species or the accepted final geometry of an earlier stage that yields one (prepare, mace, dft), and exactly the options of its kind: mace (model, weight_sha256, device, dtype, fmax_ev_per_angstrom, max_steps), dft (a surface of functional, basis, dispersion, density_fit, cosx, grid and SCF tolerances, plus the optimiser’s convergence), hessian (the same surface, which must equal the parent optimisation’s, and displacement_bohr) or dlpno (method, basis, aux_basis, thresholds as the published LoosePNO, NormalPNO or TightPNO, frozen_core, triples_mode). A model’s licence acknowledgement is never part of a recipe. A recipe’s campaigns record the vibe-scheduler campaigns compiled from it: id, directory, campaign_sha256, when it was created, whether the operator model_license_acknowledged, and which species, stage and role each node stands for. What each stage sends to vibe-qc, and what it refuses, is on the input writers page.

A constraint has a kind (distance, angle, dihedral or position), the 0-based atoms it names in the species’ conformer (2, 3, 4 or 1 of them: the drawing’s atoms first, then the hydrogens) and an optional value in angstrom or degrees; without one, the starting geometry’s value is kept. A position fixes an atom where it is.

A geometry task sets none of the calculation fields. Nothing in the specification names a program: each writer translates it, and refuses what it cannot express.

An edit in provenance.edits records the revision it produced, when (at), the actor (human, agent or system), the client it came through (browser, cli, rest, mcp) and a one-line summary.

What validation checks

The model is strict, so a mistake is reported instead of silently dropped:

  • Unknown fields are refused, at every level except inside the canvas data.

  • Identifiers start with a letter and are unique across species, reactions and jobs. Every species a reaction or a job names exists.

  • No species appears twice in one role of a reaction. A reaction’s amounts name its own participants, once each, with positive values and one way to the amount.

  • A conformer has one position per atom, three finite coordinates each, and known element symbols.

  • A nanostructure’s key matches its parameters, its first conformer has its formula, and its bonds join distinct atoms of that conformer once each.

  • For species with conformers, charge and multiplicity agree with the conformer’s atoms: the charge leaves a non-negative electron count, and the number of unpaired electrons (multiplicity minus one) cannot exceed that count and must have its parity. A methyl-radical conformer declared as a singlet is refused.

Without conformers, the model does not compute electron count from SMILES for this check. The browser/REST species-edit operation separately checks a requested multiplicity against the available SMILES. A manually assembled species passing model validation therefore has fewer chemistry guarantees than an edit made through that operation.

The JSON schema expresses the structural rules. The cross-references and the charge and spin rule are checks it cannot express, so a client that has only the schema should still validate through vibe-sketcher validate or POST /api/documents/validate before trusting a document.

Validation does not prove that a hand-edited normalized species list matches the embedded canvas. For drawing-based workflows, derive species and reactions from the canvas, or import through the supported chemistry interfaces. Nor does a valid document prove a balanced or feasible reaction, a converged geometry, or that a particular input writer supports its job specification. Those checks belong to their respective operations.

Reading and writing

vibe-sketcher validate scheme.vsk
from vibesketch.document import load, save

document = load("scheme.vsk")
save(document, "scheme-copy.vsk")  # written atomically

VSK files are written with two-space indentation, unescaped Unicode and a trailing newline. Saving normalizes JSON formatting and fills model defaults; it does not preserve arbitrary input whitespace or key order. Once serialized in this canonical form, unchanged documents diff cleanly under version control. QVF is a binary archive and does not have this textual representation.

Saving over a QVF result raises vibesketch.document.SaveRefused. The importer reads its structures but does not keep its calculation payloads; choose a new QVF or VSK destination. Native sketch archives can be saved in place when all stored data is reproducible from the sketch. Unknown data, additional metadata and damaged files require another destination too.

vibesketch.document.qvf.overwrite_refusal(data) inspects existing archive bytes and returns a reason, or None when replacement is supported. vibesketch.document.write_output(path, content) applies that policy to exported bytes and writes atomically, checking for a changed QVF destination before replacement. Use it when writing the bytes returned by an exporter.

File-writing guarantees

save and write_output write a temporary file beside the destination and atomically replace it, so an interrupted write does not leave a partly written sketch. The parent directory must exist. Existing file permissions are kept; new files use the process’s permission mask. Saving through a symbolic link preserves the link and writes to its resolved target.

Atomic replacement alone does not prevent overwriting another editor’s changes. SDK callers can capture digest_of(path) from vibesketch.document.io and pass it as expect to save or write_output; a changed destination is then refused. Without expect, an ordinary VSK save does not compare the file with the version you originally loaded. The server’s file-backed Save and the desktop track external-file changes and refuse replacement when their recorded bytes no longer match. See save conflict handling. QVF-result protection applies independently of the optional digest check.

A new output filename is also not an automatic backup. Keep earlier versions explicitly when you need them. Exporting a file does not change the source server document’s backing path or saved revision.

JSON serialization from Python

Use document.to_json_data() for a JSON-compatible dictionary and vibesketch.document.dumps(document) for VSK text. These helpers use the file format’s field aliases: schema rather than the Python attribute schema_id, and yield rather than yield_. They also serialize timestamps correctly and preserve null values within the opaque canvas data.

import json
from vibesketch.document import new_document

document = new_document()
payload = document.to_json_data()
assert payload["schema"] == "vibe-sketch/1"
text = json.dumps(payload)

A plain model_dump() is a Python representation; it is not the recommended wire format for clients posting documents to REST. Use save for files rather than duplicating serialization and overwrite handling yourself.

Versioning

The major version is part of the schema value. A change that would make an existing document invalid, or change the meaning of a field, is a new major version with a migration; that decision is the maintainer’s (AGENTS.md). Because unknown fields are refused, adding a field is also a schema change, and the committed JSON schema changes with it in the same commit.

Retrieved run results

A run may carry optional result metadata for absolute energies and thermochemistry, bound to its job specification and input species. Existing schema v1 documents remain valid. Reaction energies are computed from an explicit selection; see results and provenance.

Schematic surfaces

The additive surfaces list records typed drawing partners and their anchors, derived from KET image metadata. It is empty for older documents. See Surfaces, electrodes and nanoparticles for the schema, grafting and export limitations. These records are not atomistic or periodic models.