diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..57e11e37
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,34 @@
+POSTGRES_DB=samosachaat
+POSTGRES_USER=samosachaat_admin
+POSTGRES_PASSWORD=localdev
+DATABASE_URL=postgresql+asyncpg://samosachaat_admin:localdev@localhost:5432/samosachaat
+
+FRONTEND_PORT=3000
+AUTH_PORT=8001
+CHAT_API_PORT=8002
+INFERENCE_PORT=8003
+GRAFANA_PORT=3001
+PROMETHEUS_PORT=9090
+LOKI_PORT=3100
+
+AUTH_SERVICE_URL=http://auth:8001
+CHAT_API_URL=http://chat-api:8002
+INFERENCE_SERVICE_URL=http://inference:8003
+NEXTAUTH_URL=http://localhost:3000
+
+GOOGLE_CLIENT_ID=your-google-client-id
+GOOGLE_CLIENT_SECRET=your-google-client-secret
+GITHUB_CLIENT_ID=your-github-client-id
+GITHUB_CLIENT_SECRET=your-github-client-secret
+
+JWT_ALGORITHM=RS256
+JWT_PRIVATE_KEY=generate-an-rs256-private-key
+JWT_PUBLIC_KEY=generate-the-matching-rs256-public-key
+NEXTAUTH_SECRET=generate-a-random-secret
+INTERNAL_API_KEY=generate-a-random-internal-api-key
+
+HF_TOKEN=your-huggingface-token
+MODEL_STORAGE_PATH=./models
+DEFAULT_MODEL_TAG=samosachaat-d12
+NANOCHAT_DTYPE=float32
+NUM_WORKERS=1
diff --git a/.gitignore b/.gitignore
index ad7db224..1229f1a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,9 +4,17 @@ __pycache__/
dev-ignore/
report.md
eval_bundle/
+models/
+.terraform/
+*.tfstate
+*.tfstate.*
+*.tfvars
+*.tfvars.json
# Secrets
.env
+.env.local
+.env.*.local
# Local setup
CLAUDE.md
diff --git a/README.md b/README.md
index 79b12df3..a2ad401b 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,30 @@ nanochat is the simplest experimental harness for training LLMs. It is designed
For questions about the repo, I recommend either using [DeepWiki](https://deepwiki.com/karpathy/nanochat) from Devin/Cognition to ask questions about the repo, or use the [Discussions tab](https://github.com/karpathy/nanochat/discussions), or come by the [#nanochat](https://discord.com/channels/1020383067459821711/1427295580895314031) channel on Discord.
+## Platform monorepo scaffold
+
+This repository now also carries the samosaChaat platform scaffold alongside the
+original nanochat training code. The new top-level directories are organized for
+service-oriented development:
+
+- `services/` contains the frontend, auth, chat API, and inference services
+- `contracts/` contains the shared OpenAPI and JSON schema contracts
+- `db/migrations/` contains PostgreSQL schema bootstrap scripts
+- `helm/` contains application and observability chart scaffolding
+- `terraform/` contains shared modules plus `dev`, `uat`, and `prod` environment scaffolding
+
+### Local platform quick start
+
+1. Copy `.env.example` to `.env` and replace the placeholder secrets.
+2. Start the local stack with `bash scripts/local-dev.sh`.
+3. Seed the local database with `bash scripts/seed-db.sh`.
+4. Open the placeholder frontend at [http://localhost:3000](http://localhost:3000).
+
+The initial scaffold keeps the service topology stable for local development.
+`services/auth`, `services/chat-api`, and `services/frontend` are placeholders
+until their dedicated implementations land; the inference service is extracted on
+the follow-up branch.
+
## Time-to-GPT-2 Leaderboard
Presently, the main focus of development is on tuning the pretraining stage, which takes the most amount of compute. Inspired by the modded-nanogpt repo and to incentivise progress and community collaboration, nanochat maintains a leaderboard for a "GPT-2 speedrun", which is the wall-clock time required to train a nanochat model to GPT-2 grade capability, as measured by the DCLM CORE score. The [runs/speedrun.sh](runs/speedrun.sh) script always reflects the reference way to train GPT-2 grade model and talk to it. The current leaderboard looks as follows:
diff --git a/contracts/openapi/auth-api.yaml b/contracts/openapi/auth-api.yaml
new file mode 100644
index 00000000..192e9ae6
--- /dev/null
+++ b/contracts/openapi/auth-api.yaml
@@ -0,0 +1,108 @@
+openapi: 3.1.0
+info:
+ title: samosaChaat Auth API
+ version: 0.1.0
+ description: >
+ Contract skeleton for the authentication service. OAuth providers, session
+ exchange, and user identity endpoints must conform to this document.
+servers:
+ - url: http://auth:8001
+paths:
+ /health:
+ get:
+ summary: Readiness probe for the auth service.
+ security: []
+ responses:
+ "200":
+ description: Auth service health.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ ready:
+ type: boolean
+ required:
+ - status
+ - ready
+ /auth/oauth/{provider}/start:
+ get:
+ summary: Begin an OAuth login flow.
+ parameters:
+ - $ref: "#/components/parameters/OAuthProvider"
+ responses:
+ "302":
+ description: Redirect to the provider authorization page.
+ /auth/oauth/{provider}/callback:
+ get:
+ summary: Complete an OAuth login flow.
+ parameters:
+ - $ref: "#/components/parameters/OAuthProvider"
+ - in: query
+ name: code
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Session established.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ user:
+ $ref: ../schemas/user.json
+ access_token:
+ type: string
+ required:
+ - user
+ - access_token
+ /auth/me:
+ get:
+ summary: Return the authenticated user profile.
+ security:
+ - sessionCookie: []
+ responses:
+ "200":
+ description: Current user profile.
+ content:
+ application/json:
+ schema:
+ $ref: ../schemas/user.json
+ /auth/token/refresh:
+ post:
+ summary: Exchange a refresh token for a new access token.
+ responses:
+ "200":
+ description: Refreshed session token pair.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ access_token:
+ type: string
+ expires_in:
+ type: integer
+ required:
+ - access_token
+ - expires_in
+components:
+ parameters:
+ OAuthProvider:
+ in: path
+ name: provider
+ required: true
+ schema:
+ type: string
+ enum:
+ - google
+ - github
+ securitySchemes:
+ sessionCookie:
+ type: apiKey
+ in: cookie
+ name: session
diff --git a/contracts/openapi/chat-api.yaml b/contracts/openapi/chat-api.yaml
new file mode 100644
index 00000000..ffa7b908
--- /dev/null
+++ b/contracts/openapi/chat-api.yaml
@@ -0,0 +1,153 @@
+openapi: 3.1.0
+info:
+ title: samosaChaat Chat API
+ version: 0.1.0
+ description: >
+ Contract skeleton for conversations, persisted message history, and chat
+ orchestration between the frontend and inference service.
+servers:
+ - url: http://chat-api:8002
+security:
+ - bearerAuth: []
+paths:
+ /health:
+ get:
+ summary: Readiness probe for the chat API.
+ security: []
+ responses:
+ "200":
+ description: Chat API health.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ ready:
+ type: boolean
+ required:
+ - status
+ - ready
+ /conversations:
+ get:
+ summary: List the current user's conversations.
+ responses:
+ "200":
+ description: Conversation collection.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: ../schemas/conversation.json
+ post:
+ summary: Create a new conversation.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ title:
+ type: string
+ model_tag:
+ type: string
+ required:
+ - title
+ - model_tag
+ responses:
+ "201":
+ description: Conversation created.
+ content:
+ application/json:
+ schema:
+ $ref: ../schemas/conversation.json
+ /conversations/{conversationId}:
+ get:
+ summary: Fetch a single conversation.
+ parameters:
+ - $ref: "#/components/parameters/ConversationId"
+ responses:
+ "200":
+ description: Conversation details.
+ content:
+ application/json:
+ schema:
+ $ref: ../schemas/conversation.json
+ patch:
+ summary: Update mutable conversation metadata.
+ parameters:
+ - $ref: "#/components/parameters/ConversationId"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ title:
+ type: string
+ model_tag:
+ type: string
+ responses:
+ "200":
+ description: Updated conversation.
+ content:
+ application/json:
+ schema:
+ $ref: ../schemas/conversation.json
+ /conversations/{conversationId}/messages:
+ get:
+ summary: List persisted messages for a conversation.
+ parameters:
+ - $ref: "#/components/parameters/ConversationId"
+ responses:
+ "200":
+ description: Conversation message history.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: ../schemas/message.json
+ post:
+ summary: Append a message and begin streaming completion.
+ parameters:
+ - $ref: "#/components/parameters/ConversationId"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ $ref: ../schemas/message.json
+ stream:
+ type: boolean
+ default: true
+ required:
+ - message
+ responses:
+ "200":
+ description: Server-sent token stream from the inference service.
+ content:
+ text/event-stream:
+ schema:
+ type: string
+components:
+ parameters:
+ ConversationId:
+ in: path
+ name: conversationId
+ required: true
+ schema:
+ type: string
+ format: uuid
+ securitySchemes:
+ bearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
diff --git a/contracts/openapi/inference-api.yaml b/contracts/openapi/inference-api.yaml
new file mode 100644
index 00000000..017cf41b
--- /dev/null
+++ b/contracts/openapi/inference-api.yaml
@@ -0,0 +1,174 @@
+openapi: 3.1.0
+info:
+ title: samosaChaat Inference API
+ version: 0.1.0
+ description: >
+ Contract skeleton for the standalone inference microservice that streams
+ tokens and manages model weight lifecycle.
+servers:
+ - url: http://inference:8003
+paths:
+ /health:
+ get:
+ summary: Liveness and readiness probe.
+ responses:
+ "200":
+ description: Inference service health.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ ready:
+ type: boolean
+ current_model:
+ type:
+ - string
+ - "null"
+ required:
+ - status
+ - ready
+ /generate:
+ post:
+ summary: Stream generated tokens as server-sent events.
+ security:
+ - internalApiKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/GenerateRequest"
+ responses:
+ "200":
+ description: SSE response of token chunks and done marker.
+ content:
+ text/event-stream:
+ schema:
+ type: string
+ /models:
+ get:
+ summary: List available and loaded model weights.
+ security:
+ - internalApiKey: []
+ responses:
+ "200":
+ description: Model registry view.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ current_model:
+ type:
+ - string
+ - "null"
+ models:
+ type: array
+ items:
+ $ref: "#/components/schemas/ModelInfo"
+ required:
+ - current_model
+ - models
+ /models/swap:
+ post:
+ summary: Drain workers and swap the loaded model weights.
+ security:
+ - internalApiKey: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ model_tag:
+ type: string
+ required:
+ - model_tag
+ responses:
+ "202":
+ description: Swap request accepted.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ current_model:
+ type: string
+ required:
+ - status
+ - current_model
+ /stats:
+ get:
+ summary: Worker pool and throughput statistics.
+ security:
+ - internalApiKey: []
+ responses:
+ "200":
+ description: Runtime worker statistics.
+ content:
+ application/json:
+ schema:
+ type: object
+components:
+ securitySchemes:
+ internalApiKey:
+ type: apiKey
+ in: header
+ name: X-Internal-API-Key
+ schemas:
+ ChatMessage:
+ type: object
+ additionalProperties: false
+ properties:
+ role:
+ type: string
+ content:
+ type: string
+ required:
+ - role
+ - content
+ GenerateRequest:
+ type: object
+ additionalProperties: false
+ properties:
+ messages:
+ type: array
+ items:
+ $ref: "#/components/schemas/ChatMessage"
+ temperature:
+ type:
+ - number
+ - "null"
+ max_tokens:
+ type:
+ - integer
+ - "null"
+ top_k:
+ type:
+ - integer
+ - "null"
+ required:
+ - messages
+ ModelInfo:
+ type: object
+ additionalProperties: false
+ properties:
+ model_tag:
+ type: string
+ source:
+ type: string
+ path:
+ type: string
+ loaded:
+ type: boolean
+ required:
+ - model_tag
+ - source
+ - path
+ - loaded
diff --git a/contracts/schemas/conversation.json b/contracts/schemas/conversation.json
new file mode 100644
index 00000000..4c401c1e
--- /dev/null
+++ b/contracts/schemas/conversation.json
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "Conversation",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "user_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "title": {
+ "type": "string"
+ },
+ "model_tag": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "required": [
+ "id",
+ "user_id",
+ "title",
+ "model_tag",
+ "created_at",
+ "updated_at"
+ ]
+}
diff --git a/contracts/schemas/message.json b/contracts/schemas/message.json
new file mode 100644
index 00000000..df9e551b
--- /dev/null
+++ b/contracts/schemas/message.json
@@ -0,0 +1,47 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "Message",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "conversation_id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "role": {
+ "type": "string"
+ },
+ "content": {
+ "type": "string"
+ },
+ "token_count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "model_tag": {
+ "type": "string"
+ },
+ "inference_time_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "required": [
+ "id",
+ "conversation_id",
+ "role",
+ "content",
+ "token_count",
+ "model_tag",
+ "inference_time_ms",
+ "created_at"
+ ]
+}
diff --git a/contracts/schemas/user.json b/contracts/schemas/user.json
new file mode 100644
index 00000000..e6b57ff9
--- /dev/null
+++ b/contracts/schemas/user.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "title": "User",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "email": {
+ "type": "string",
+ "format": "email"
+ },
+ "name": {
+ "type": "string"
+ },
+ "avatar_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "uri"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "provider_id": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_login_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ },
+ "required": [
+ "id",
+ "email",
+ "name",
+ "avatar_url",
+ "provider",
+ "provider_id",
+ "created_at",
+ "updated_at",
+ "last_login_at"
+ ]
+}
diff --git a/db/migrations/0001_initial_schema.sql b/db/migrations/0001_initial_schema.sql
new file mode 100644
index 00000000..bb40f32e
--- /dev/null
+++ b/db/migrations/0001_initial_schema.sql
@@ -0,0 +1,42 @@
+CREATE EXTENSION IF NOT EXISTS pgcrypto;
+
+CREATE TABLE IF NOT EXISTS users (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ email TEXT NOT NULL UNIQUE,
+ name TEXT NOT NULL,
+ avatar_url TEXT,
+ provider TEXT NOT NULL,
+ provider_id TEXT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ last_login_at TIMESTAMPTZ
+);
+
+CREATE UNIQUE INDEX IF NOT EXISTS users_provider_lookup_idx
+ ON users (provider, provider_id);
+
+CREATE TABLE IF NOT EXISTS conversations (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ title TEXT NOT NULL,
+ model_tag TEXT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX IF NOT EXISTS conversations_user_id_idx
+ ON conversations (user_id);
+
+CREATE TABLE IF NOT EXISTS messages (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
+ role TEXT NOT NULL,
+ content TEXT NOT NULL,
+ token_count INTEGER NOT NULL DEFAULT 0,
+ model_tag TEXT NOT NULL,
+ inference_time_ms INTEGER NOT NULL DEFAULT 0,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+
+CREATE INDEX IF NOT EXISTS messages_conversation_id_idx
+ ON messages (conversation_id, created_at);
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..51a5360b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,98 @@
+services:
+ postgres:
+ image: postgres:15
+ restart: unless-stopped
+ environment:
+ POSTGRES_DB: ${POSTGRES_DB:-samosachaat}
+ POSTGRES_USER: ${POSTGRES_USER:-samosachaat_admin}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-localdev}
+ ports:
+ - "5432:5432"
+ volumes:
+ - pgdata:/var/lib/postgresql/data
+ - ./db/migrations:/docker-entrypoint-initdb.d:ro
+
+ frontend:
+ build:
+ context: ./services/frontend
+ restart: unless-stopped
+ ports:
+ - "${FRONTEND_PORT:-3000}:3000"
+ environment:
+ AUTH_SERVICE_URL: ${AUTH_SERVICE_URL:-http://auth:8001}
+ CHAT_API_URL: ${CHAT_API_URL:-http://chat-api:8002}
+ NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
+ depends_on:
+ - auth
+ - chat-api
+
+ auth:
+ build:
+ context: ./services/auth
+ restart: unless-stopped
+ ports:
+ - "${AUTH_PORT:-8001}:8001"
+ environment:
+ DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://samosachaat_admin:localdev@postgres:5432/samosachaat}
+ GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
+ GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
+ GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
+ GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
+ JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY:-}
+ JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY:-}
+ depends_on:
+ - postgres
+
+ chat-api:
+ build:
+ context: ./services/chat-api
+ restart: unless-stopped
+ ports:
+ - "${CHAT_API_PORT:-8002}:8002"
+ environment:
+ DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://samosachaat_admin:localdev@postgres:5432/samosachaat}
+ AUTH_SERVICE_URL: ${AUTH_SERVICE_URL:-http://auth:8001}
+ INFERENCE_SERVICE_URL: ${INFERENCE_SERVICE_URL:-http://inference:8003}
+ INTERNAL_API_KEY: ${INTERNAL_API_KEY:-}
+ depends_on:
+ - postgres
+ - auth
+ - inference
+
+ inference:
+ build:
+ context: ./services/inference
+ restart: unless-stopped
+ ports:
+ - "${INFERENCE_PORT:-8003}:8003"
+ environment:
+ MODEL_STORAGE_PATH: /models
+ DEFAULT_MODEL_TAG: ${DEFAULT_MODEL_TAG:-samosachaat-d12}
+ NANOCHAT_DTYPE: ${NANOCHAT_DTYPE:-float32}
+ HF_TOKEN: ${HF_TOKEN:-}
+ INTERNAL_API_KEY: ${INTERNAL_API_KEY:-}
+ NUM_WORKERS: ${NUM_WORKERS:-1}
+ volumes:
+ - ./models:/models
+
+ grafana:
+ image: grafana/grafana:latest
+ restart: unless-stopped
+ ports:
+ - "${GRAFANA_PORT:-3001}:3000"
+
+ prometheus:
+ image: prom/prometheus:latest
+ restart: unless-stopped
+ ports:
+ - "${PROMETHEUS_PORT:-9090}:9090"
+
+ loki:
+ image: grafana/loki:latest
+ restart: unless-stopped
+ command: -config.file=/etc/loki/local-config.yaml
+ ports:
+ - "${LOKI_PORT:-3100}:3100"
+
+volumes:
+ pgdata:
diff --git a/helm/README.md b/helm/README.md
new file mode 100644
index 00000000..cf577202
--- /dev/null
+++ b/helm/README.md
@@ -0,0 +1,4 @@
+# Helm Charts
+
+This directory holds Kubernetes deployment charts for the samosaChaat platform
+and its supporting observability stack.
diff --git a/helm/observability/Chart.yaml b/helm/observability/Chart.yaml
new file mode 100644
index 00000000..071fb150
--- /dev/null
+++ b/helm/observability/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: observability
+description: Observability chart scaffold for Grafana, Prometheus, and Loki.
+type: application
+version: 0.1.0
+appVersion: "0.1.0"
diff --git a/helm/observability/templates/NOTES.txt b/helm/observability/templates/NOTES.txt
new file mode 100644
index 00000000..9e750d08
--- /dev/null
+++ b/helm/observability/templates/NOTES.txt
@@ -0,0 +1,4 @@
+The observability chart scaffold is in place.
+
+Add concrete manifests for Grafana, Prometheus, Loki, dashboards, and scrape
+configuration as the platform monitoring stack is implemented.
diff --git a/helm/observability/values.yaml b/helm/observability/values.yaml
new file mode 100644
index 00000000..013a3646
--- /dev/null
+++ b/helm/observability/values.yaml
@@ -0,0 +1,8 @@
+grafana:
+ enabled: true
+
+prometheus:
+ enabled: true
+
+loki:
+ enabled: true
diff --git a/helm/samosachaat/Chart.yaml b/helm/samosachaat/Chart.yaml
new file mode 100644
index 00000000..08a4ce2f
--- /dev/null
+++ b/helm/samosachaat/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: samosachaat
+description: Application chart scaffold for the samosaChaat platform.
+type: application
+version: 0.1.0
+appVersion: "0.1.0"
diff --git a/helm/samosachaat/templates/NOTES.txt b/helm/samosachaat/templates/NOTES.txt
new file mode 100644
index 00000000..74a5ef53
--- /dev/null
+++ b/helm/samosachaat/templates/NOTES.txt
@@ -0,0 +1,4 @@
+The samosaChaat application chart is scaffolded.
+
+Populate this chart with Deployments, Services, Ingress, Secrets, and ConfigMaps
+as the platform services are implemented.
diff --git a/helm/samosachaat/values.yaml b/helm/samosachaat/values.yaml
new file mode 100644
index 00000000..2f26a777
--- /dev/null
+++ b/helm/samosachaat/values.yaml
@@ -0,0 +1,10 @@
+image:
+ repository: ghcr.io/manmohan659/nanochat/frontend
+ tag: latest
+ pullPolicy: IfNotPresent
+
+replicaCount: 1
+
+service:
+ type: ClusterIP
+ port: 3000
diff --git a/scripts/local-dev.sh b/scripts/local-dev.sh
new file mode 100755
index 00000000..f635bd15
--- /dev/null
+++ b/scripts/local-dev.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+ENV_FILE="${ROOT_DIR}/.env"
+
+if [[ ! -f "${ENV_FILE}" ]]; then
+ ENV_FILE="${ROOT_DIR}/.env.example"
+ echo "Using ${ENV_FILE} because .env is not present."
+fi
+
+exec docker compose --env-file "${ENV_FILE}" up --build "$@"
diff --git a/scripts/seed-db.sh b/scripts/seed-db.sh
new file mode 100755
index 00000000..8d0be3f2
--- /dev/null
+++ b/scripts/seed-db.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+ENV_FILE="${ROOT_DIR}/.env"
+
+if [[ ! -f "${ENV_FILE}" ]]; then
+ ENV_FILE="${ROOT_DIR}/.env.example"
+fi
+
+set -a
+source "${ENV_FILE}"
+set +a
+
+docker compose exec -T postgres psql \
+ -U "${POSTGRES_USER:-samosachaat_admin}" \
+ -d "${POSTGRES_DB:-samosachaat}" <<'SQL'
+INSERT INTO users (
+ id,
+ email,
+ name,
+ avatar_url,
+ provider,
+ provider_id,
+ last_login_at
+) VALUES (
+ '11111111-1111-1111-1111-111111111111',
+ 'demo@samosachaat.local',
+ 'Demo User',
+ 'https://example.com/avatar.png',
+ 'github',
+ 'demo-user',
+ NOW()
+) ON CONFLICT (id) DO NOTHING;
+
+INSERT INTO conversations (
+ id,
+ user_id,
+ title,
+ model_tag
+) VALUES (
+ '22222222-2222-2222-2222-222222222222',
+ '11111111-1111-1111-1111-111111111111',
+ 'Welcome to samosaChaat',
+ 'samosachaat-d12'
+) ON CONFLICT (id) DO NOTHING;
+
+INSERT INTO messages (
+ id,
+ conversation_id,
+ role,
+ content,
+ token_count,
+ model_tag,
+ inference_time_ms
+) VALUES (
+ '33333333-3333-3333-3333-333333333333',
+ '22222222-2222-2222-2222-222222222222',
+ 'assistant',
+ 'The database scaffold is ready for local development.',
+ 9,
+ 'samosachaat-d12',
+ 12
+) ON CONFLICT (id) DO NOTHING;
+SQL
diff --git a/services/auth/Dockerfile b/services/auth/Dockerfile
new file mode 100644
index 00000000..bce37ff9
--- /dev/null
+++ b/services/auth/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+COPY README.md /app/README.md
+
+EXPOSE 8001
+
+CMD ["python", "-m", "http.server", "8001", "--bind", "0.0.0.0"]
diff --git a/services/auth/README.md b/services/auth/README.md
new file mode 100644
index 00000000..97433de7
--- /dev/null
+++ b/services/auth/README.md
@@ -0,0 +1,7 @@
+# Auth Service
+
+Scaffold placeholder for Issue #5.
+
+The monorepo branch provisions this directory and a minimal Docker image so
+local `docker compose up` remains viable before the real auth service
+implementation lands.
diff --git a/services/chat-api/Dockerfile b/services/chat-api/Dockerfile
new file mode 100644
index 00000000..1cbef5b5
--- /dev/null
+++ b/services/chat-api/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+COPY README.md /app/README.md
+
+EXPOSE 8002
+
+CMD ["python", "-m", "http.server", "8002", "--bind", "0.0.0.0"]
diff --git a/services/chat-api/README.md b/services/chat-api/README.md
new file mode 100644
index 00000000..a671d66b
--- /dev/null
+++ b/services/chat-api/README.md
@@ -0,0 +1,7 @@
+# Chat API Service
+
+Scaffold placeholder for Issue #6.
+
+This container is intentionally minimal on the monorepo scaffold branch. It
+keeps the compose topology stable while the dedicated chat API implementation
+is developed.
diff --git a/services/frontend/Dockerfile b/services/frontend/Dockerfile
new file mode 100644
index 00000000..14c93d73
--- /dev/null
+++ b/services/frontend/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+COPY index.html /app/index.html
+
+EXPOSE 3000
+
+CMD ["python", "-m", "http.server", "3000", "--bind", "0.0.0.0"]
diff --git a/services/frontend/index.html b/services/frontend/index.html
new file mode 100644
index 00000000..cae9179e
--- /dev/null
+++ b/services/frontend/index.html
@@ -0,0 +1,65 @@
+
+
+
+
+
+ samosaChaat frontend scaffold
+
+
+
+
+ samosaChaat platform scaffold
+
+ The monorepo structure is in place. This placeholder frontend keeps
+ docker compose up usable until the real UI lands.
+
+
+ - Auth service:
http://localhost:8001
+ - Chat API:
http://localhost:8002
+ - Inference:
http://localhost:8003
+
+
+
+
diff --git a/services/inference/Dockerfile b/services/inference/Dockerfile
new file mode 100644
index 00000000..271d6416
--- /dev/null
+++ b/services/inference/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+COPY README.md /app/README.md
+
+EXPOSE 8003
+
+CMD ["python", "-m", "http.server", "8003", "--bind", "0.0.0.0"]
diff --git a/services/inference/README.md b/services/inference/README.md
new file mode 100644
index 00000000..8bcb6dce
--- /dev/null
+++ b/services/inference/README.md
@@ -0,0 +1,6 @@
+# Inference Service
+
+Scaffold placeholder for Issue #3.
+
+This branch only creates the monorepo structure. The next branch extracts the
+FastAPI inference microservice into this directory.
diff --git a/terraform/README.md b/terraform/README.md
new file mode 100644
index 00000000..dd92fc00
--- /dev/null
+++ b/terraform/README.md
@@ -0,0 +1,6 @@
+# Terraform
+
+Environment and module scaffolding for the samosaChaat AWS platform.
+
+- `environments/` holds per-environment stacks for `dev`, `uat`, and `prod`
+- `modules/` holds reusable building blocks for shared infrastructure
diff --git a/terraform/environments/dev/README.md b/terraform/environments/dev/README.md
new file mode 100644
index 00000000..e8a0247e
--- /dev/null
+++ b/terraform/environments/dev/README.md
@@ -0,0 +1,6 @@
+# dev
+
+Development environment stack scaffold.
+
+Use this directory to wire together the shared Terraform modules for the dev
+AWS account and domain configuration.
diff --git a/terraform/environments/prod/README.md b/terraform/environments/prod/README.md
new file mode 100644
index 00000000..5428bb2d
--- /dev/null
+++ b/terraform/environments/prod/README.md
@@ -0,0 +1,6 @@
+# prod
+
+Production environment stack scaffold.
+
+Use this directory to compose the shared Terraform modules with production
+networking, security, and availability requirements.
diff --git a/terraform/environments/uat/README.md b/terraform/environments/uat/README.md
new file mode 100644
index 00000000..a2cd31c4
--- /dev/null
+++ b/terraform/environments/uat/README.md
@@ -0,0 +1,6 @@
+# uat
+
+User acceptance testing environment stack scaffold.
+
+Use this directory to compose the shared Terraform modules with UAT-specific
+variables, state backends, and deployment topology.
diff --git a/terraform/modules/acm/README.md b/terraform/modules/acm/README.md
new file mode 100644
index 00000000..72f42139
--- /dev/null
+++ b/terraform/modules/acm/README.md
@@ -0,0 +1,3 @@
+# acm module
+
+Scaffold placeholder for the shared ACM certificate module.
diff --git a/terraform/modules/ecr/README.md b/terraform/modules/ecr/README.md
new file mode 100644
index 00000000..82fb7bcd
--- /dev/null
+++ b/terraform/modules/ecr/README.md
@@ -0,0 +1,3 @@
+# ecr module
+
+Scaffold placeholder for the shared container registry module.
diff --git a/terraform/modules/efs/README.md b/terraform/modules/efs/README.md
new file mode 100644
index 00000000..2524aaa3
--- /dev/null
+++ b/terraform/modules/efs/README.md
@@ -0,0 +1,3 @@
+# efs module
+
+Scaffold placeholder for the shared EFS storage module.
diff --git a/terraform/modules/eks/README.md b/terraform/modules/eks/README.md
new file mode 100644
index 00000000..cdef5f16
--- /dev/null
+++ b/terraform/modules/eks/README.md
@@ -0,0 +1,3 @@
+# eks module
+
+Scaffold placeholder for the shared EKS cluster module.
diff --git a/terraform/modules/iam/README.md b/terraform/modules/iam/README.md
new file mode 100644
index 00000000..1f836ca7
--- /dev/null
+++ b/terraform/modules/iam/README.md
@@ -0,0 +1,3 @@
+# iam module
+
+Scaffold placeholder for the shared IAM roles and policy module.
diff --git a/terraform/modules/rds/README.md b/terraform/modules/rds/README.md
new file mode 100644
index 00000000..74451ef4
--- /dev/null
+++ b/terraform/modules/rds/README.md
@@ -0,0 +1,3 @@
+# rds module
+
+Scaffold placeholder for the shared PostgreSQL / RDS module.
diff --git a/terraform/modules/route53/README.md b/terraform/modules/route53/README.md
new file mode 100644
index 00000000..b6b0c8e1
--- /dev/null
+++ b/terraform/modules/route53/README.md
@@ -0,0 +1,3 @@
+# route53 module
+
+Scaffold placeholder for the shared DNS management module.
diff --git a/terraform/modules/vpc/README.md b/terraform/modules/vpc/README.md
new file mode 100644
index 00000000..d324fa85
--- /dev/null
+++ b/terraform/modules/vpc/README.md
@@ -0,0 +1,3 @@
+# vpc module
+
+Scaffold placeholder for the shared VPC networking module.