Prefer the simplest thing that satisfies the invariants. Ask before introducing a new dependency, a new service, or a new database.
Resources
6Install
npx skillscat add codered-vigneshvar/ojas Install via the SkillsCat registry.
Ojas Engineering Skill
This file is read first by any AI agent working in this repo. Follow it strictly.
Product context
Ojas is an AI-native patient workspace. A doctor opens a patient profile, dumps in reports/audio/prescriptions/lab results/handwritten notes, and asks anything. The AI extracts, stores, and retrieves patient information with mandatory source citations. Per-patient scoping is a hard invariant — the AI assistant must only see one patient's data at a time. Cross-patient queries go through a separate structured layer.
Hard invariants (never violate)
- Per-patient scoping at the query level. Every vector or relational query that feeds the AI MUST filter by
patient_idin the SQL WHERE clause, not in application code, not in post-processing. - Every AI-surfaced fact must have a
source_artifact_id. Citations are non-negotiable. If a fact can't be cited, it shouldn't be displayed as a fact — show it as raw context. - Audit log every AI query and every data access. Use
core/audit.py. No exceptions. - Patient data never leaves India in prod. All cloud services must be in an Indian region. Local LLM in dev. Cloud LLMs only behind an explicit env flag (
ALLOW_CLOUD_LLM=true). - Consent must exist before any AI processing. Check
consentstable before invoking LLMs on a patient's data. - Never log raw patient data. Logs may include IDs and metadata, never PHI.
Architecture rules
- Layered: routes (thin) → services (business logic) → repositories (data access) → models (ORM).
- Routes are thin. No business logic in route handlers. They parse input, call a service, return a response.
- Services are pure-ish. They take typed inputs and return typed outputs. They don't reach into request objects.
- Repositories own SQL. No SQL in services. No SQLAlchemy in routes.
- LLM, STT, storage are behind interfaces. Never import vllm or boto3 directly in service code. Use
llm.base.LLMClient,stt.base.STTClient,storage.base.ObjectStorage.
Code conventions
- Python: ruff format, strict mypy, async-first for I/O. Type everything. No
Anywithout a comment explaining why. - TypeScript: strict mode, no
any. Components are function components with hooks. Server state in TanStack Query, client state in Zustand. - Imports: absolute imports in Python (
from ojas.services...), absolute in TS via@/alias. - Naming: snake_case Python, camelCase TS, PascalCase components and types, SCREAMING_SNAKE for constants.
- File size: if a file exceeds ~300 lines, consider splitting.
- No premature abstraction. Inline first, extract on second use.
Data model rules
- Every table has
id,created_at,updated_at. - Every patient-scoped table has
patient_idwith a FK and an index. - Every extracted entity (medications, diagnoses, etc.) has
source_artifact_idFK. - Use JSONB for long-tail extracted fields, typed columns for everything queried relationally.
- Migrations only via Alembic. Never edit schemas by hand. One concern per migration.
Prompts
- Prompts live in
apps/api/src/ojas/llm/prompts/as versioned files (e.g.extract_medications_v3.txt). - Never inline prompts in service code longer than 3 lines.
- Every prompt has a header describing inputs, outputs, and the eval cases that cover it.
Testing rules
- Every service method has a unit test.
- Every route has at least one integration test against a real test DB.
- AI behavior is tested through the evals harness, not unit tests. Mock LLMs in unit tests.
make testmust pass before any commit.
Eval rules
- Any change to a prompt, model, retrieval logic, or extraction pipeline requires re-running
make evals. - New features add new eval cases.
- Eval failures block merges.
Tech stack (do not change without an ADR)
- Postgres 16 + pgvector (no MongoDB, no separate vector DB)
- FastAPI + SQLAlchemy 2.0 async + Alembic + Pydantic v2
- React + Vite + TS + TanStack Query + Zustand + shadcn/ui
- Redis + Arq for background jobs
- MinIO local, S3-compatible interface
- vLLM serving Qwen2.5 in dev; cloud LLMs behind
ALLOW_CLOUD_LLMenv flag - Langfuse for LLM tracing
- uv for Python packages, pnpm for Node packages
Git and commit rules
- Never add Claude as a co-author. Do not include
Co-Authored-By: Claudeor any Anthropic AI attribution in commit messages. - Commit messages should describe the why, not the what.
- Stage specific files — never
git add .orgit add -A. - Never skip hooks (
--no-verify) without explicit instruction. - Never force-push to
main.
When making changes
- Read this file first.
- Read the relevant
docs/decisions/*.mdif your change touches architecture. - If you're introducing a new pattern, write an ADR in
docs/decisions/. - Update docs alongside code changes.
- Update this file if you discover a convention worth encoding.
When unsure
Prefer the simplest thing that satisfies the invariants. Ask before introducing a new dependency, a new service, or a new database.