Rules
Contract for every contributor β human or AI β working in this repo.
Project Overview
automAIta is a deployment-ready boilerplate for HuggingFace Spaces.
- Backend: FastAPI (MVC) managed by
uv, defined inbackend/pyproject.toml. - Frontend: Vite + React + TypeScript + Tailwind + PWA. No app frameworks.
- Container: one Docker image, single port
7860. FastAPI serves the built React bundle as static files at/and the API under/api/*. - Deploy: GitHub
mainβ.github/workflows/sync-to-hf.ymlmirrors the repo to a HuggingFace Space, which rebuilds automatically.
Scope & Goals
- Stay a boilerplate. The only "feature" is
App.tsxproving the/api/statusround-trip works. - New projects fork this repo and fill it in β they should not have to rewire Docker, ports, the workflow, or the layout.
Coding Rules
- No hardcoded values. Backend goes through
app/core/config.py(env prefixAUTOMAITA_). Frontend uses Vite env vars (import.meta.env.VITE_*) when needed. - Backend MVC layering:
controllers/βAPIRouters only. Thin: parse, call a service, return.services/β business logic. No FastAPI imports.schemas/β pydantic request/response DTOs.models/β pydantic domain models.core/β config, lifespan, anything cross-cutting.
- Frontend:
- TypeScript
strictis on. Don't downgrade it. - Tailwind is the only styling system. No Material UI, Chakra, Mantine, shadcn, etc., without explicit approval.
- No state libraries (Redux/Zustand/Jotai) or routers (React Router/TanStack Router) until the project actually needs one.
- All API calls go through
src/api/client.ts.
- TypeScript
- Single-port contract. The container exposes only
7860. Don't introduce nginx, supervisord, or a second process. FastAPI mounts the static SPA itself. - Same-origin in prod. API client uses relative URLs (
/api/...). Don't hardcodehttp://localhost:8000or any HF URL.
Agent Behavior Rules
- Never bypass the
frontend/βbackend/boundary β they communicate over HTTP, not shared imports. - Never break the single-port contract or the non-root UID 1000 Dockerfile.
- Never edit
.github/workflows/sync-to-hf.ymlto add deploy logic that belongs inside the container β the workflow's job is mirroring, nothing else. - Preserve the Dockerfile's two-stage shape (
frontend-buildβruntime). New build steps go inside an existing stage; don't add stages without need. - When adding a backend route, create a controller in
app/controllers/, register it inapp/main.py, and put the logic inapp/services/.
Workflow Rules
- Local dev (split):
- Backend:
cd backend && uv sync && uv run uvicorn app.main:app --reload --port 8000 - Frontend:
cd frontend && npm install && npm run dev(proxy handles/api).
- Backend:
- Container parity (run what HF runs):
docker build -t automaita . && docker run --rm -p 7860:7860 automaitaβ http://localhost:7860. - Deploy: push to GitHub
main. Required repo secrets:HF_TOKEN,HF_USER,HF_SPACE_NAME. - Don't push directly to the HF Space repo β let the action do it, otherwise you fork history.
Conventions
- Env var prefix:
AUTOMAITA_(backend),VITE_(frontend). - API prefix: all backend routes live under
/api/*. The PWA service worker explicitly excludes/api/*from navigation fallback. - Filenames:
- Backend: snake_case modules, controllers suffixed
_controller.py, services suffixed_service.py. - Frontend: PascalCase for components (
App.tsx), camelCase for utilities (client.ts).
- Backend: snake_case modules, controllers suffixed
- Health:
/api/healthis the simplest possible OK probe;/api/statusis the rich one (used by the SPA on mount). - Python: 3.12+,
rufffor linting (config inpyproject.toml). - Node: 20+ (matches the Dockerfile build stage).
HuggingFace Space deployment
- The HF Space's README is generated at CI time by
.github/workflows/sync-to-hf.ymlfrom the template at.github/hf-space.README.tpl.md. Don't hand-edit the repo'sREADME.mdexpecting it to ship to HF β edit the template instead. The repo'sREADME.mdis purely for GitHub readers and is overwritten in the workflow's checkout (never pushed back to GitHub). - Required GitHub repo secrets (Settings β Secrets and variables β Actions β Secrets):
HF_TOKENβ HF user access token, write scope.HF_USERβ HF username or org that owns the Space.HF_SPACE_NAMEβ the Space's repo name (nouser/prefix).
- Optional GitHub repo variables (Settings β Secrets and variables β Actions β Variables) β each falls back to a default if unset:
HF_SPACE_TITLE,HF_SPACE_EMOJI,HF_COLOR_FROM,HF_COLOR_TO,HF_APP_PORT,HF_SHORT_DESCRIPTION.
- Persistent storage lives at
/data. It's mounted at runtime only if persistent storage is enabled in the Space's Settings UI. Without it,/datastill exists and is writable, but writes are wiped on every restart. The boilerplate works in both modes. /datais runtime-only per HF docs β never write to/datafrom the Dockerfile (build step). Pre-create + chown is fine; writes are not.- Do not use
VOLUME /datain the Dockerfile β HF Spaces don't honorVOLUMEdeclarations and it complicates layer permissions. - Container must run as UID 1000.
$HOMEmust be writable by that user (we setHOME=/home/appin the Dockerfile so libraries likehuggingface_hubcan cache to~/.cache/huggingface). - Build-time secrets (private model downloads, etc.) use
--mount=type=secret,id=NAMEand are read from/run/secrets/NAMEper HF docs. Never pass runtime secrets throughARGβ they would bake into image layers. - Runtime secrets/variables set in the HF Space Settings UI become regular env vars in the container β read them via
os.environ.get(...)or viapydantic-settings(AUTOMAITA_*prefix for our config). - The backend reports storage status under
storagein/api/status(exists,writable,persistent). Thepersistentflag is a heuristic: it'struewhen a marker file from a previous startup survived to the current one.
Out of Scope
- Auth, database, persistence, queues, LLM integrations β add per project, don't bake into the boilerplate.
- Tests β the boilerplate has nothing meaningful to test. Add tests when you add logic.
- Analytics, telemetry, error tracking β opt in per project.
- HF Hub Storage Buckets (
hf://buckets/...) β that's a separate object-storage product accessed viahuggingface_hub, not a filesystem mount. Not wired in by default; add the lib + auth if you need it per project.