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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|---|
|
|
The format and its major version. |
|
integer, at least 0 |
Incremented by the server on every accepted edit; an edit names the revision it read. |
|
object |
|
|
list |
The molecules and ions in the drawing. |
|
list |
The steps of the scheme. |
|
list |
Calculations to run. |
|
list |
Saved staged workflows over species; absent when there are none. |
|
list |
Atoms marked in colour for the canvas and figures; absent when there are none. |
|
list |
R-group cores and bracketed groups the drawing shows, derived from it; absent when there are none. |
|
list |
The R-groups’ definitions, derived from the drawing; absent when there are none. |
|
object |
|
|
object |
|
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:
|
Example |
Meaning |
|---|---|---|
|
|
|
|
|
Required for every task but |
|
|
Basis sets. |
|
|
Integral approximation. |
|
|
Dispersion correction. |
|
|
Implicit solvation, |
|
|
Excited states; required for |
|
|
Coordinates an optimisation holds fixed; |
|
|
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.