Vishwakarma — The Divine Architect (System Design)
Vishwakarma governs how systems are shaped before a line of code is written.
Defaults for a startup
- Boring technology bias: Postgres, Redis, FastAPI, React. Each novel technology is an innovation token — spend at most one per project, on the thing that differentiates you.
- Monolith-first. One deployable Python backend, one frontend. Split a service out only when a specific pain (independent scaling, team ownership, isolation) demands it — never preemptively.
- Optimize for deletability: small modules with narrow interfaces are easy to remove or rewrite.
Module boundaries (domain-driven-lite)
- Organize by domain, not by layer:
billing/,ingestion/,agents/— notmodels/,services/,utils/at top level. - Dependency direction is one-way: core domain logic never imports adapters (DB clients, HTTP clients, LLM SDKs). Adapters import the core. Enforce with
import-linter(Python) /eslint-plugin-boundaries(TS). - Cross-module access goes through the module's public interface (its
__init__.py/index.tsexports), never deep imports. utils/is a code smell. If it grows past a few files, its contents belong in a domain.
Configuration (12-factor)
- All config from environment variables; typed and validated at startup (
pydantic-settingsin Python,zodonprocess.envin TS). Crash on missing config, don't limp. - One
.env.examplekept current. No environment-specificif PROD:branches in business logic.
API design
- REST conventions: plural nouns (
/invoices), standard verbs, standard status codes (201 on create, 404 not 200-with-error). - Version from day one:
/v1/prefix. Breaking changes get/v2/, never a silent change. - Every endpoint has typed request/response models (Pydantic / zod) — validation at the boundary, trusted types inside.
Queue vs sync
- Sync if: < 2 s, the caller needs the result to proceed, failure should surface to the user immediately.
- Queue (Celery/arq + Redis) if: > 2 s, retryable, fan-out, or the caller only needs an acknowledgment. LLM batch work is always queued.
- Every queued job is idempotent and has a dead-letter path (see
hanuman).
AI-native specifics
- Separate the prompt layer from business logic: prompts live in their own module with typed inputs/outputs; business code calls
summarize(doc) -> Summary, never builds prompt strings inline. - Design for model swappability: one gateway interface over providers (or LiteLLM); model IDs and temperatures in config, not code.
- Mark human-in-the-loop points explicitly in the design: which outputs ship straight to users vs require review, and what the review UI is.
- Eval-driven development: before building an LLM feature, define the golden dataset and pass criteria (see
agni). No evals, no feature.
Before building — checklist
- Fits the monolith, or written justification for a new service
- Module placed in a domain; dependencies point inward
- Config via validated env vars,
.env.exampleupdated - API versioned with typed request/response models
- Sync/queue decision made against the 2 s rule
- LLM calls behind the prompt layer, with evals defined