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 .obj format, stored under Workflows/ or Exports/
  • A .artifact.json sidecar file — adjacent to the mesh, containing artifactId, 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/ or Exports/

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:

  1. File extension — .glb/.gltf maps to 3D model preview capability
  2. Sidecar presence — existence of .rigmeta.json upgrades mesh to rigged-mesh
  3. Metadata completeness — availability of artifactId enables 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:

  1. Writing geometry to Exports/ or Workflows/ subdirectories
  2. Generating fresh artifactId and versionId values
  3. 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.json sidecars 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →