How Modly's Artifact Registry Manages and Persists Generated 3D Meshes Across Sessions
Modly persists generated 3D meshes by storing mesh files alongside JSON sidecar files in the workspace, then scanning and rebuilding the asset library on demand to survive application restarts.
The Modly artifact registry (implemented in lightningpixel/modly) provides a file-system-backed persistence layer for 3D mesh generation workflows. Unlike database-centric approaches, the registry treats the workspace directory as the single source of truth, enabling meshes to survive crashes, version upgrades, and cross-session collaboration without complex migration logic.
Core Persistence Model: Mesh Files Plus Sidecar Metadata
Modly's artifact registry uses a dual-file strategy to track generated meshes. When a workflow node produces a 3D mesh, it writes:
- The mesh file itself — typically
.glb,.gltf, or.objformat, stored underWorkflows/orExports/ - A
.artifact.jsonsidecar file — adjacent to the mesh, containingartifactId,versionId, provenance chain, and manifest references
For rigged meshes, an additional .rigmeta.json file may appear with skeletal metadata. This design decouples binary asset storage from searchable metadata, allowing the registry to rebuild its index without parsing large geometry files.
In src/shared/types/artifacts.ts, the provenance structure captures generation lineage:
// From artifacts.ts — defines what travels with each generated mesh
interface ArtifactProvenance {
artifactId: string; // Stable identifier across versions
versionId: string; // Specific generation instance
sourceWorkspacePath?: string; // Input that produced this mesh
manifestWorkspacePath?: string; // Workflow definition
}
The sidecar pattern ensures that even if the mesh file is moved or archived externally, its origin and relationships remain discoverable.
Workspace Scanning and Asset Discovery
When the application initializes or when the UI refreshes, the Artifact Registry Service performs a complete filesystem scan rather than maintaining an in-memory database. This approach guarantees consistency with the actual workspace state.
The scan operates through listWorkspaceAssetLibrary() in electron/main/artifact-registry-service.ts:
// Simplified flow of the registry's discovery process
async function listWorkspaceAssetLibrary(workspaceRoot: string): Promise<AssetLibraryEntry[]> {
const roots = ['Workflows', 'Exports'];
const candidates = await Promise.all(
roots.map(root => collectFiles(path.join(workspaceRoot, root)))
);
return candidates
.flat()
.filter(p => !isSidecarFile(p)) // Exclude .artifact.json, .rigmeta.json
.map(p => classifyAssetLibraryCandidate(p)) // Determine capability: mesh, rigged-mesh, etc.
.filter(c => isSupportedCapability(c))
.map(c => buildEntry(c)); // Enrich with metadata from sidecar
}
The registry filters out internal files by testing for .artifact.json and .rigmeta.json suffixes, ensuring these metadata carriers never appear as standalone library entries.
Path Security and Normalization
Before any path enters the registry, normalizeWorkspaceAssetPath() validates:
- Path is non-empty and contains no encoded escape sequences
- Path does not traverse above the workspace root (no
../exploits) - Path resides under approved roots:
Workflows/orExports/
This security boundary prevents workflow nodes from writing meshes to arbitrary filesystem locations or reading outside the designated workspace.
Asset Classification and Capability Detection
The classifyAssetLibraryCandidate() function determines what operations each asset supports based on:
- File extension —
.glb/.gltfmaps to 3D model preview capability - Sidecar presence — existence of
.rigmeta.jsonupgradesmeshtorigged-mesh - Metadata completeness — availability of
artifactIdenables provenance tracking
Classification results feed into the AssetLibraryEntry type defined in src/shared/types/assetLibrary.ts, which carries display metadata, openability flags, and preview kind selection.
Metadata Enrichment from Sidecar Files
The readMetadata() function parses adjacent JSON sidecars to populate registry entries:
// From artifact-registry-service.ts — sidecar parsing
async function readMetadata(meshPath: string): Promise<Partial<AssetLibraryMetadata>> {
const sidecarPath = meshPath + '.artifact.json';
try {
const content = await fs.readFile(sidecarPath, 'utf-8');
const parsed = JSON.parse(content);
return {
artifactId: parsed.artifactId,
versionId: parsed.versionId,
provenance: parsed.provenance,
sourceWorkspacePath: parsed.sourceWorkspacePath,
warnings: validateProvenanceLinks(parsed) // Check for dangling references
};
} catch {
return { warnings: ['Missing or invalid artifact metadata'] };
}
}
Missing or malformed sidecars produce warning annotations rather than fatal errors, allowing legacy meshes to appear in the library with degraded provenance visibility.
IPC Interface for Cross-Process Access
The registry exposes three IPC channels in electron/main/ipc-handlers.ts via registerWorkspaceAssetLibraryIpcHandlers():
| Channel | Purpose |
|---|---|
workspace:library:list |
Retrieve all persisted mesh entries |
workspace:library:read |
Fetch metadata and preview info for specific path |
workspace:library:open |
Request editor access, validating openability |
Renderer processes invoke these channels to populate UI components without direct filesystem access:
// Example: listing persisted meshes from a React/Vue component
const library = await window.ipcRenderer.invoke('workspace:library:list');
if (library.success) {
const meshEntries = library.entries.filter(
e => e.preview.kind === '3d-model'
);
meshEntries.forEach(entry => {
console.log(`Mesh: ${entry.displayName}`);
console.log(`Artifact ID: ${entry.artifactId ?? 'untracked'}`);
console.log(`Generated: ${new Date(entry.mtimeMs).toLocaleString()}`);
});
}
Reading Specific Mesh Metadata
To inspect a particular mesh's provenance before loading it into the editor:
const result = await window.ipcRenderer.invoke('workspace:library:read', {
workspacePath: 'Exports/character-mesh.glb'
});
if (result.success) {
const { entry, preview } = result;
console.log('Capability:', entry.capability); // 'mesh' or 'rigged-mesh'
console.log('Artifact ID:', entry.artifactId); // Links to generation record
console.log('Provenance chain:', entry.provenance?.map(p => p.nodeId));
// preview.kind === '3d-model' indicates GLB/GLTF viewer compatibility
}
Opening Meshes for Editing
The workspace:library:open channel validates that an asset is openable before granting editor access:
const openResult = await window.ipcRenderer.invoke('workspace:library:open', {
workspacePath: 'Workflows/prop-export.glb'
});
if (openResult.success) {
// Proceed to initialize 3D editor with the mesh path
loadInEditor(openResult.entry.workspacePath);
} else {
// Common failure: asset capability lacks 'openable' flag
console.error('Cannot open:', openResult.error.message);
}
Mesh Generation Workflow Integration
Workflow nodes like the mesh exporter (src/areas/workflows/nodes/mesh-exporter/processor.ts) interact with the persistence layer by:
- Writing geometry to
Exports/orWorkflows/subdirectories - Generating fresh
artifactIdandversionIdvalues - Serializing provenance to adjacent
.artifact.json
This ensures every generated mesh immediately becomes discoverable through registry scans without explicit registration calls.
Summary
- Dual-file persistence — Meshes live as standard geometry files with
.artifact.jsonsidecars carrying identity and provenance - Filesystem-as-database — Registry rebuilds its index from disk scans, eliminating synchronization failures between sessions
- Security-hardened paths — All workspace-relative paths validate against traversal attacks and restricted roots
- Capability-based classification — File extension plus metadata presence determines preview and editing eligibility
- IPC-mediated access — Main process handles filesystem operations; renderer calls typed channels for library operations
Frequently Asked Questions
Where does Modly store generated 3D meshes?
Modly writes mesh files to workspace subdirectories Workflows/ or Exports/, with companion .artifact.json files in the same directory. This location is configurable per-workspace but always constrained to approved roots for security.
How does the registry handle meshes created in previous sessions?
Since listWorkspaceAssetLibrary() performs fresh filesystem scans rather than consulting a cached database, any mesh files and sidecars remaining in the workspace automatically appear in the current session's asset library, complete with their original provenance metadata.
What happens if the .artifact.json sidecar is missing or corrupted?
The registry treats missing sidecars as non-fatal warnings. The mesh file still appears in the library, but with artifactId: undefined and a warning annotation. Users can still open, preview, and edit the mesh; only provenance tracking and version-aware operations degrade.
Can multiple users share persisted meshes through version control?
Yes. Because the persistence layer uses plain files and JSON sidecars, standard Git workflows apply. Teams can commit Exports/ directories, and Modly reconstructs the full asset library state on each clone, including generation provenance and version history.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →