Document Status: Design & Ideation
Scope: FaceForge Core
Last Updated: 2026-01-13
Version: v0.2.11
This document is the authoritative design specification for FaceForge Core: what it must provide, what it intentionally avoids, and the interfaces that future work will build on.
FaceForge is designed to stay stable and boring at the center, while advanced functionality (recognition, training, integrations, rich visualizations, etc.) is delivered as optional plugins over the Core’s public APIs and job system.
Note: This specification describes desired behavior and interfaces. Where implementation details are not specified (or could vary), those areas are marked as TO-DO while preserving constraints and intent.
FaceForge is a local-first, self-hosted Asset Management System (AMS) specialized for entities (real and fictional, human and non-human) and assets related to those entities (facial maps, voice samples, attributes, relationships, etc.).
It is envisioned as a desktop-managed local services bundle:
External tools (for example, ComfyUI or StashApp) must be able to query FaceForge and retrieve assets through a stable API without depending on the UI.
A common target environment includes very large local datasets. FaceForge is expected to operate primarily on local storage while remaining compatible with remote object storage systems (like Amazon S3, Google Cloud Storage, MEGA S4, MinIO, etc.).
The Core provides stable foundations:
The Core does not own compute-heavy intelligence such as face recognition, force-directed graph rendering, or LoRA training. Those are delivered as plugins. FaceForge Core is the asset storage and management platform those capabilities connect to.
Keep the center stable The Core stays small and dependable. New capabilities should plug in through stable interfaces rather than expanding Core scope.
Transparent storage Assets must not be hidden inside opaque container volumes. Users must be able to see and manage where their data lives (local disks, NAS, remote storage, etc.). The user can control the application’s data storage broadly (running everything from a named folder path) or in a more modular way (e.g., separating database files from object storage).
“Double-click” simplicity (cross-platform) The system must be runnable by non‑developer users via a single desktop launcher experience. Container runtimes (Docker, Podman, etc.) are explicitly out of scope for end‑user deployment. All required components are managed directly by the desktop shell application.
Integration-first
The Core is designed to serve downstream tools via a stable and well documented API. Documentation of the API is provided by OpenAPI/Swagger and kept in sync with the active implementation at all times.
The specific capabilities and features of FaceForge Core are categorized and defined as follows:
Note: The visual relationship graph UI is intentionally out of scope for the Core. The Core provides the data surface for relationship metadata; rendering and interaction will live in an official plugin (FFGraph).
First-run storage UX is desktop-managed:
FACEFORGE_HOME (data directory) and ports on first run.FACEFORGE_HOME as the data root and creates its required subfolders under it.Remote S3 endpoints remain a supported design target, but the initial list of “blessed” providers and setup UX can evolve over time.
Design intent: Compute-heavy work happens out-of-process. The Core remains the job registry, API surface, and artifact store.
Core is responsible for plugin discovery, registration, configuration, permissions, and versioning. Plugins must be able to:
These named plugins are upcoming design targets that the Core must accommodate naturally. The capabilities of these various plugins give a glimpse of the Core’s ultimate role and some potential intended use cases.
FFIdentify — face detection + identity matching
Detect faces, compute embeddings, match identities across media, and emit structured results back into Core.
FFGraph — relationship graphing
Force-directed graph visualization that turns Core relationship metadata into an interactive “entities web.”
FFLoRA — LoRA training
Produces per-entity LoRAs (SDXL, SD 1.5, SD 2.x). Outputs are stored as assets with clean provenance and derived-asset links.
FFSD — Stable Diffusion / ComfyUI integration
Custom nodes that can list, fetch, and resolve LoRAs and other artifacts directly from FaceForge with a stable API contract.
FFStash — StashApp integration
Bulk-friendly import and sync workflows (performers → entities, media scanning jobs, write-back of tags/notes where configured).
FFVoice — voice cloning
Manages voice samples as assets linked to entities and produces cloning artifacts per entity.
FFDialogue — conversational agent
Builds context-aware chat agents using entity metadata + attachments, with a dedicated UI surface.
FFMeta — media library enrichment
Scans libraries, extracts faces/audio, matches identities, and writes structured outputs (tags, notes, sidecar metadata files).
FFBackup — backup/export/restore
Scheduled backups of metadata + assets, selective exports (JSON + ZIP), and restore workflows.
.gitignore rules, environment variable support, etc.).FaceForge Core does not directly:
The Core provides the platform surface those concerns plug into.
This section describes a practical minimum baseline intended to be reliable and easy to run locally without containerization.
6.1.1 Desktop Shell Orchestrator - Tauri v2 desktop app (tray + updates + system popups) - Responsible for: - Starting/stopping/monitoring managed components (Core API service, local object storage, plugin runners) - Autostart at login (optional) - Presenting status/logs and opening the web UI
6.1.2 Core Service (Python 3.11/3.12 + FastAPI + Uvicorn)
- Runs as a local HTTP API server:
- /v1/... API
- /docs and /redoc API playground
- the web UI as static assets (one service, one port)
Why SQLite (design reasoning): - Embeddable engine: no separate database service to install/manage; lifecycle is bound to the Core process. - Local-first reliability: single-file persistence with excellent durability; easy backups and restores. - Flexible fields without schema churn: admin-defined fields live in JSON; common fields can be indexed via generated columns. - Operational simplicity: fewer moving parts, fewer failure modes, and a smoother “double-click” experience.
Why SeaweedFS (design reasoning): - S3 compatibility for external tools without bundling MinIO (and its licensing/packaging concerns). - User transparency over where bucket data lives on disk. - Lightweight local services model appropriate for a self-hosted desktop-managed bundle.
This section defines the conceptual model. The physical schema is implementation work, but the invariants must hold.
Entities are the primary identity objects in the system (humans, characters, creatures, etc.).
entity_id: SHA-256 hex string (64 chars), immutabledisplay_name: stringaliases: string[]fields: JSON object for admin-defined fields (validated by field definitions)primary_thumbnail_asset_id: asset reference (optional)tags: string[]created_at, updated_at: timestampsplugin_data: JSON object keyed by plugin id (namespaced)Universal attachment model (files, models, documents, etc.).
asset_id: SHA-256 hex string (64 chars), immutableowner: { "type": "entity" | "system" | "plugin", "id": "..." }linked_entity_ids: entity_id[] (many-to-many)kind: string (examples: image.original, image.thumbnail, model.lora, doc.pdf, audio.clip)mime_type, size_bytescontent_hash: sha256 of file bytes (dedupe/integrity)storage: provider + bucket + keyderived_from_asset_id: optional provenance linkextracted_metadata: JSON (Core-generated at ingest time)user_metadata: JSON (optional)plugin_data: JSON (namespaced)Two complementary concepts:
Design intent: - The system must support new descriptor keys being defined at any time. - Historic records are not required to have those keys. - Historic records may be updated later, without migrations or schema changes.
A plugin may provide any combination of:
Each plugin ships with a manifest (example: plugin.json) containing:
id, name, versioncore_compat: semver rangecapabilities: ["api", "runner", "ui", ...]config_schema: JSON Schema (for UI-rendered settings)permissions: declared needs (read entities, write assets, create jobs, filesystem scopes, network, GPU access, etc.)routes_prefix: e.g. /v1/plugins/<id>job_types: list (namespaced)entrypoints:runner: how to start the plugin processui: route(s) exposed in the Core UI shellPhase 1 (MVP): - plugins installed as folders inside a local install dir - Core loads manifests and exposes plugin metadata via API - Desktop shell starts/stops plugin runner processes as needed - Plugins communicate with Core via HTTP: - create jobs - stream logs/progress - upload artifacts (assets) and link them to entities - UI renders navigation based on manifest + enabled status
Phase 2 (later):
- install from URL/local .whl/.zip
- per-plugin virtualenvs (and potentially per-plugin Python versions)
- signed bundles + marketplace
TO-DO: Bundle signing, distribution, sandboxing strategy, and permission enforcement depth need a separate design doc when in scope.
/v1/...9.2.1 Entities
- GET /v1/entities
- POST /v1/entities
- GET /v1/entities/{entity_id}
- PATCH /v1/entities/{entity_id}
- DELETE /v1/entities/{entity_id} (soft delete recommended)
9.2.2 Descriptors
- GET /v1/entities/{entity_id}/descriptors
- POST /v1/entities/{entity_id}/descriptors
- PATCH /v1/descriptors/{descriptor_id}
- DELETE /v1/descriptors/{descriptor_id}
9.2.3 Field Definitions (Admin)
- GET /v1/admin/field-defs
- POST /v1/admin/field-defs
- PATCH /v1/admin/field-defs/{field_key}
- DELETE /v1/admin/field-defs/{field_key}
9.2.4 Assets / Attachments
- POST /v1/assets/upload (multipart; supports companion _meta.json)
- POST /v1/assets/bulk-import (directory import job; reads *_meta.json sidecars)
- GET /v1/assets/{asset_id}
- GET /v1/assets/{asset_id}/download (stream; must support HTTP range)
- POST /v1/entities/{entity_id}/assets/{asset_id}/link
- POST /v1/entities/{entity_id}/assets/{asset_id}/unlink
9.2.5 Relationships
- GET /v1/relationships?entity_id=...
- POST /v1/relationships
- DELETE /v1/relationships/{relationship_id}
- GET /v1/relation-types?query=sp
9.2.6 Jobs
- POST /v1/jobs
- GET /v1/jobs/{job_id}
- GET /v1/jobs/{job_id}/log
- POST /v1/jobs/{job_id}/cancel
9.2.7 Plugins
- GET /v1/plugins
- POST /v1/plugins/{id}/enable
- POST /v1/plugins/{id}/disable
- GET /v1/plugins/{id}/config
- PUT /v1/plugins/{id}/config
Stable asset listing & download
- GET /v1/entities/{entity_id}/assets?kind=model.lora
- GET /v1/assets/{asset_id}/download supports large files and resumable downloads (HTTP range).
Bulk operations
- POST /v1/entities/bulk-upsert (idempotent; external IDs supported)
- POST /v1/jobs must support large inputs via stored “job input assets” (avoid giant JSON payloads)
TO-DO: The exact shape of external IDs and bulk-upsert conflict resolution rules require specification.
All persistent data is stored in one or more user-controlled, user-defined directories.
FACEFORGE_HOME is the data directory root for FaceForge.
FACEFORGE_HOME set to the user-selected path.FACEFORGE_HOME can be supplied via environment variable.FACEFORGE_HOME is not set, Core uses a deterministic per-user OS data directory (never the process working directory):%LOCALAPPDATA%\\FaceForge~/Library/Application Support/FaceForge$XDG_DATA_HOME/faceforge (or ~/.local/share/faceforge)On startup, Core ensures these subfolders exist under FACEFORGE_HOME:
${FACEFORGE_HOME}/db (SQLite metadata database directory)${FACEFORGE_HOME}/db/core.sqlite3${FACEFORGE_HOME}/assets (filesystem-backed asset storage)${FACEFORGE_HOME}/s3 (local S3/SeaweedFS data directory)${FACEFORGE_HOME}/s3/seaweedfs${FACEFORGE_HOME}/logs (log files)${FACEFORGE_HOME}/tmp (temporary files)${FACEFORGE_HOME}/config (configuration files)${FACEFORGE_HOME}/config/core.json (Core configuration)${FACEFORGE_HOME}/config/ports.json (launcher-selected ports)${FACEFORGE_HOME}/tools (user-managed tool overrides, e.g. ExifTool)${FACEFORGE_HOME}/plugins (plugin folders containing plugin.json manifests)Core supports path overrides via ${FACEFORGE_HOME}/config/core.json:
paths.db_dir, paths.s3_dir, paths.logs_dir, paths.plugins_dir (absolute or relative-to-FACEFORGE_HOME).config/ and tmp/.Core uses JSON configuration at ${FACEFORGE_HOME}/config/core.json. Current shape (v1):
auth.install_token: per-install token required for non-health endpointsnetwork.bind_host, network.core_port, network.seaweed_s3_portpaths.* (optional path overrides)tools.exiftool_enabled, tools.exiftool_path (optional override)storage.routing + storage.s3 (routing and S3 endpoint credentials)seaweed.* (optional Core-managed SeaweedFS; Desktop typically orchestrates SeaweedFS)The Core UI covers all Core features:
http://127.0.0.1:<port> or view it inside the desktop shell window.Minimum UX expectations:
UI delivery is intentionally runtime-light:
This section defines the desired end-user experience for running FaceForge Core locally and the specific design decisions intended to reduce friction in that process as much as possible.
${FACEFORGE_HOME}/config/ports.json${FACEFORGE_HOME}/runtime/ports.jsonA desktop launcher is a first-class deliverable.
Release UX requirements:
- ship a FaceForge Desktop app (Tauri) that behaves like a real app:
- first-run wizard for choosing FACEFORGE_HOME and Core port
- starts/monitors required components (Core API service, SeaweedFS services, plugin runners)
- exposes only the UI/API port
- minimizes to system tray instead of exiting when the window is closed
- tray menu: Open UI, Status, Logs, Stop, Restart, Exit
- on exit: prompt whether to stop services or leave them running
User-facing simplification goals: - one installer per OS (or portable zip where appropriate) - one desktop launcher app - one user-chosen data directory - no loose scripts
Documentation is written for non-engineers first and includes screenshots of first-run and tray controls.
TO-DO: Update strategy for the Core service, SeaweedFS, and plugin bundles.
This section standardizes how developers produce bundled artifacts locally for testing and validation.
Scope: “bundling” here means producing:
Prerequisites (Windows):
py launcher)cargo install tauri-cli --locked)Note: All repo scripts are designed to run with the repo-local
.venvand will create it automatically if missing.
scripts/ are currently Windows-first (they assume a Windows-style .venv layout in some places).cargo tauri build, but release-grade “Core sidecar bundling” is currently standardized only for the Windows MSI pipeline.Bundle Core manually (macOS/Linux):
python3.12 -m venv .venv
./.venv/bin/python -m pip install --upgrade pip
./.venv/bin/python -m pip install -e ./core
./.venv/bin/python -m pip install pyinstaller
cd core
../.venv/bin/python -m PyInstaller pyinstaller.spec --noconfirm --distpath dist --workpath build
Expected outputs (macOS/Linux):
core/dist/ contains the built Core executable (typically faceforge-core without a .exe extension).core/build/ contains PyInstaller work files.Build Desktop installers (macOS/Linux):
cd desktop/src-tauri
cargo tauri build
Expected outputs (macOS/Linux):
desktop/src-tauri/target/release/bundle/ contains OS-specific installer artifacts (subfolder names and file extensions vary by OS).From the repo root:
./scripts/check-core.ps1
./scripts/build-core.ps1
What it does:
.venv exists (creates it if needed).venv (editable) and installs PyInstallerPyInstaller using core/pyinstaller.specPrimary outputs:
core/dist/faceforge-core.exe (stable path; preferred output for all callers)Secondary outputs (implementation detail):
core/build/ (PyInstaller workdir)core/dist-* and/or core/build-* may be created only if you opt in to timestamp fallbackCommon options:
./scripts/build-core.ps1 -KeepBuildHistory keeps old build-*/dist-* folders../scripts/build-core.ps1 -AllowTimestampFallback uses dist-YYYYMMDD-HHMMSS if core/dist is locked.This creates an installer that bundles the desktop shell plus the Core binary as a sidecar.
1) Build Core (produces core/dist/faceforge-core.exe):
./scripts/build-core.ps1
2) Stage the Core binary into Desktop’s Tauri bundle inputs:
New-Item -ItemType Directory -Force -Path 'desktop/src-tauri/binaries' | Out-Null
Copy-Item -Force 'core/dist/faceforge-core.exe' 'desktop/src-tauri/binaries/faceforge-core-x86_64-pc-windows-msvc.exe'
3) Build the Desktop installer:
Push-Location 'desktop/src-tauri'
cargo tauri build
Pop-Location
Primary outputs (Windows):
desktop/src-tauri/target/release/bundle/msi/*.msiNote: The exact MSI filename includes the app name and version; treat
*.msiunder that folder as the output.
This section standardizes how new releases are cut and what happens inside GitHub Actions when release assets are produced.
1) Ensure your working tree is clean and CI is green locally:
./scripts/check-core.ps1
2) Choose the new version number (SemVer: X.Y.Z).
3) Bump versions across Core + Desktop in one go:
./scripts/set-version.ps1 -Version X.Y.Z
This updates, at minimum:
core/pyproject.tomlcore/src/faceforge_core/app.pydesktop/src-tauri/Cargo.tomldesktop/src-tauri/tauri.conf.json4) Update human-facing release notes (as appropriate):
RELEASE_NOTES.md5) Commit the version bump + notes:
git add -A
git commit -m "chore(release): vX.Y.Z"
6) Create and push the tag (release automation expects a v-prefixed tag):
git tag vX.Y.Z
git push origin HEAD
git push origin vX.Y.Z
7) Create a GitHub Release for tag vX.Y.Z and publish it.
Alternative: you may run the release workflow manually via
workflow_dispatchand providetag: vX.Y.Z.
When a release is published (or the workflow is manually dispatched), GitHub Actions runs .github/workflows/release-core.yml.
Sequence (Windows release assets job):
1) Check out the repository at the release tag.
2) Ensure core/pyinstaller.spec exists (for legacy tags it may be fetched from the default branch).
3) Sync version metadata from the tag (best-effort safety net).
4) Set up Python 3.12.
5) Set up Rust toolchain.
6) Install Tauri CLI (cargo install tauri-cli --locked).
7) Install WiX toolset (MSI tooling).
8) Build the Core executable by running ./scripts/build-core.ps1.
9) Collect/normalize Core output into core/dist/faceforge-core.exe.
10) Stage Core into Desktop sidecar binaries:
- desktop/src-tauri/binaries/faceforge-core-x86_64-pc-windows-msvc.exe
11) Build the Desktop MSI via cargo tauri build.
12) Collect release artifacts into artifacts/release/:
- Desktop *.msi
- faceforge-core.exe
- SHA256SUMS.txt (SHA-256 hashes of the files above)
13) Upload these files to the GitHub Release as downloadable assets.
Published release assets (expected):
faceforge-core.exeFaceForge*.msiSHA256SUMS.txt