Repository Structure
Purpose
This document defines the monorepo strategy for Farmerspec projects. This structure is designed to be operated by AI agents, where each agent has a clear scope and minimal need to navigate outside their designated areas.
AI-First Design Philosophy
This repository structure is optimized for AI agent operation. The key principles:
- Clear boundaries - Each agent knows exactly which folders they own
- Minimal context switching - Agents don't jump between unrelated folders
- Self-contained work areas - Everything an agent needs is co-located
- Predictable patterns - Same structure repeated at each level
- Progressive disclosure - Documentation at every level for agent context
- Language isolation - TypeScript stays in
apps/, Python stays inservices/
Why This Matters for AI Agents
AI agents work best when they have: - Focused scope - Less context to load, fewer distractions - Clear ownership - No ambiguity about what to modify - Co-located files - Tests next to code, docs next to implementation - Consistent patterns - Learn once, apply everywhere - Language purity - No mixing Python and TypeScript in the same tree
The Farmerspec Agent System
Farmerspec orchestrates 10 AI agents that work sequentially on features. Each agent has specific folders they read and write.
Agent Overview
Feature Request
│
▼
┌─────────────────┐
│ Baron │ Classify, route, create feature structure
└────────┬────────┘
│
▼
┌─────────────────┐
│ Veuve │ Answer product questions (if needed)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Duc │ Architecture decisions, technical specs
└────────┬────────┘
│
▼
┌─────────────────┐
│ Marie │ Write ALL tests BEFORE implementation
└────────┬────────┘
│
▼
┌─────────────────┐
│ Dede │ Backend implementation (make tests pass)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Dali │ Frontend implementation (make tests pass)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Gus │ GitOps/Infrastructure (make tests pass)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Victor │ Documentation review (flag issues)
└────────┬────────┘
│
▼
┌─────────────────┐
│ General │ Code review, create PR
└────────┬────────┘
│
▼
┌─────────────────┐
│ Socrate │ Retrospective, improve agents
└─────────────────┘
Agent Folder Ownership
| Agent | Role | Primary Folders (Read/Write) | Reference Folders (Read Only) |
|---|---|---|---|
| Baron | Classifier & Router | specs/, .farmerspec/ |
All (to understand scope) |
| Veuve | Product Expert | specs/*/product/ |
docs/ |
| Duc | Architect | specs/*/architecture/, docs/architecture/ |
All (to understand system) |
| Marie | Test Writer | services/**/tests/, apps/**/tests/, tests/ |
specs/*/architecture/ |
| Dede | Backend Dev | services/ (entire Python world) |
specs/, tests (to make pass) |
| Dali | Frontend Dev | apps/ (entire TypeScript world) |
specs/, tests (to make pass) |
| Gus | GitOps Engineer | infra/, platform/ |
specs/, tests (to make pass) |
| Victor | Librarian | docs/ (flags issues only) |
All docs/ and README.md files |
| General | Code Reviewer | .github/ |
All (to review) |
| Socrate | Retrospective | .farmerspec/retros/ |
All (to analyze) |
Agent Deep Dive
Baron - The Classifier
Role: Receives feature requests, classifies them, creates the feature structure.
Writes to:
- specs/<issue-number>/ - Creates feature folder
- .farmerspec/state.yaml - Tracks workflow state
Why this structure helps Baron:
- specs/ is a single, predictable location for all features
- Numbered folders prevent conflicts
- State file provides clear workflow tracking
Veuve - The Product Expert
Role: Answers product questions, clarifies requirements, defines acceptance criteria.
Writes to:
- specs/<issue>/product/ - Product decisions, user stories
Reads from:
- docs/ - Existing product documentation
- specs/<issue>/ - Current feature context
Why this structure helps Veuve:
- Product knowledge is centralized in docs/
- Feature-specific decisions stay in specs/<issue>/product/
- Clear separation from technical specs
Duc - The Architect
Role: Makes architecture decisions, writes technical specifications, updates global documentation.
Writes to:
- specs/<issue>/architecture/ - Feature-specific architecture
- docs/architecture/ - Global architecture updates
- docs/services/<domain>/ - Domain documentation
Reads from: - All folders (to understand the full system)
Why this structure helps Duc:
- docs/architecture/ is the single source of truth for system design
- specs/<issue>/architecture/ keeps feature decisions contained
- Domain-grouped services make boundaries clear
Marie - The Test Writer
Role: Writes ALL tests BEFORE any implementation. This is true TDD - tests define the contract.
Writes to:
- services/<domain>/<service>/tests/unit/ - Backend unit tests
- services/<domain>/<service>/tests/integration/ - Backend integration tests
- apps/<app>/tests/unit/ - Frontend unit tests
- apps/<app>/tests/component/ - Frontend component tests
- tests/e2e/ - End-to-end tests
- tests/contract/ - Contract tests
- tests/journey/ - User journey tests
- tests/infra/ - Infrastructure tests
Reads from:
- specs/<issue>/architecture/ - What to test
- docs/ - Existing patterns
Why this structure helps Marie:
- Tests are co-located with the code they test (for unit/integration)
- Cross-cutting tests have their own dedicated folder (tests/)
- Clear markers (@pytest.mark.unit, etc.) for test categorization
- Marie never touches src/ - only tests/
Critical rule: Marie writes tests, implementation agents make them pass. Marie CANNOT modify tests after handoff.
Dede - The Backend Developer
Role: Implements backend code to make Marie's tests pass.
Writes to:
- services/<domain>/<service>/src/ - Backend implementation
- services/<domain>/<service>/docs/ - Backend documentation
- services/_packages/ - Shared Python code
Reads from:
- services/<domain>/<service>/tests/ - Tests to make pass
- specs/<issue>/architecture/ - Technical specs
Cannot modify:
- tests/ - Tests are immutable after Marie writes them
- apps/ - Frontend is Dali's domain (pure TypeScript)
- infra/ - Infrastructure is Gus's domain
Why this structure helps Dede:
- Never leaves services/ - Everything Dede needs is there
- Tests are right next to the code: src/ and tests/ are siblings
- Shared Python code is in services/_packages/, not a separate root folder
- Domain grouping prevents Dede from accidentally touching other domains
Dali - The Frontend Developer
Role: Implements frontend code to make Marie's tests pass.
Writes to:
- apps/<app>/src/ - Frontend implementation
- apps/<app>/docs/ - Frontend documentation
- apps/_packages/ - Shared TypeScript code (except generated-types/)
Reads from:
- apps/<app>/tests/ - Tests to make pass
- specs/<issue>/architecture/ - Technical specs
- apps/_packages/generated-types/ - Generated types from Python models (READ ONLY)
Cannot modify:
- tests/ - Tests are immutable after Marie writes them
- services/ - Backend is Dede's domain (pure Python)
- infra/ - Infrastructure is Gus's domain
- apps/_packages/generated-types/ - Generated by CI scripts
Why this structure helps Dali:
- Never leaves apps/ - Everything Dali needs is there
- Tests are right next to the code
- UI components are in apps/_packages/ui-library/
- Clear separation from backend concerns
- No Python files to accidentally modify
Gus - The GitOps Engineer
Role: Implements infrastructure and deployment to make Marie's infra tests pass.
Writes to:
- infra/terraform/modules/ - Terraform modules
- infra/terraform/environments/ - Environment configs
- infra/k8s/base/ - Base K8s manifests
- infra/k8s/overlays/ - Environment overlays
- infra/k8s/appsets/ - ArgoCD ApplicationSets
- platform/ - Platform service configs and deployment scripts
Reads from:
- tests/infra/ - Infra tests to make pass
- specs/<issue>/architecture/ - Technical specs
- services/ - To understand what needs deploying
- apps/ - To understand what needs deploying
Cannot modify:
- services/*/src/ - Application code
- apps/*/src/ - Application code
- Other agents' tests
Special responsibilities:
- Type Generation - Gus owns the CI workflow that generates TypeScript types from Python Pydantic models
- Infrastructure Hydration - Gus runs
scripts/hydrate-infra.shbefore any deployment
Why this structure helps Gus:
- All infrastructure is in infra/
- Clear separation: terraform/ for cloud, k8s/ for GitOps
- platform/ configs are separate from deployment specs
- K8s manifests mirror the service structure (infra/k8s/base/<domain>/ maps to services/<domain>/)
- Hydration pattern keeps K8s manifests self-contained
Victor - The Librarian
Role: Reviews ALL documentation for coherence, accuracy, and completeness. Flags issues but does NOT fix them.
Reads from:
- All README.md files
- All docs/ folders at every level
- specs/ - Feature specifications
Writes to:
- specs/<issue>/docs-review.md - Review report with flagged issues
Cannot modify: - Any documentation directly (only flags issues)
Why this structure helps Victor:
- Progressive disclosure means docs exist at every level
- Consistent pattern: every folder has README.md + docs/
- Clear ownership: Victor knows who to escalate each issue to
- Single review report keeps findings organized
Doc ownership for escalation:
| Documentation | Owner |
|---|---|
docs/architecture/ |
Duc |
services/*/docs/ |
Dede |
apps/*/docs/ |
Dali |
infra/*/docs/ |
Gus |
platform/*/docs/ |
Gus |
tests/docs/ |
Marie |
Root docs/ |
Duc |
General - The Code Reviewer
Role: Reviews all code changes, ensures quality, creates the PR.
Reads from:
- All modified files
- specs/<issue>/ - Feature context
- .farmerspec/state.yaml - Workflow state
Writes to:
- .github/ - PR creation
- specs/<issue>/review.md - Review notes
Why this structure helps General: - Can see all changes in context - Feature specs provide the "why" - Clear workflow state shows what each agent did
Socrate - The Philosopher
Role: Runs after EVERY issue completes. Analyzes the workflow, identifies improvements, updates agent prompts.
Reads from:
- All folders (to analyze what happened)
- specs/<issue>/ - The complete feature journey
- .farmerspec/ - Agent prompts and templates
Writes to:
- .farmerspec/retros/<issue>.md - Retrospective report
- .farmerspec/improvements.yaml - Proposed improvements
- Agent prompts (with human approval)
Why this structure helps Socrate: - Complete visibility into the entire workflow - Retrospectives are stored for learning - Improvements are tracked and versioned
Folder Architecture
Language Separation: The Core Principle
This monorepo enforces strict language separation:
| World | Folder | Language | Tooling | Owner |
|---|---|---|---|---|
| 🟢 TypeScript World | apps/ |
TypeScript/React | pnpm, Vite | Dali |
| 🔵 Python World | services/ |
Python | uv, pytest, ruff | Dede |
| 🟠 Infrastructure World | infra/ |
HCL, YAML | Terraform, Kustomize | Gus |
Why strict separation?
- Simplified tooling - No need to tell Python linters to ignore TypeScript files
- Cleaner CI pipelines - "If change is in
apps/, runpnpm test" - Agent clarity - Dali never leaves
apps/, Dede never leavesservices/ - No mixed packages - Shared code lives with its language
apps/ - TypeScript World (Frontend)
The apps/ folder is a pure TypeScript/Node.js workspace. It contains all frontend applications AND shared TypeScript packages.
Why subfolders in apps?
Different applications serve different purposes: - PWA - Progressive Web App for mobile-first users - Portal - Web portal for stakeholders/admins - Dashboard - Analytics/reporting interface - Marketing - Public marketing site
Each app is independently deployable with its own build, Dockerfile, and test suite.
apps/ # 🟢 TYPESCRIPT WORLD (pnpm workspace)
├── README.md
├── docs/ # Shared frontend patterns
├── package.json # Root workspace config
├── pnpm-workspace.yaml
├── turbo.json # Turbo monorepo config
├── tsconfig.base.json # Shared TS config
│
├── _packages/ # 📦 Shared TypeScript Code
│ ├── ui-library/ # Shared React components
│ │ ├── package.json
│ │ ├── src/
│ │ └── tests/
│ │
│ ├── ts-utils/ # Logging, formatting, helpers
│ │ ├── package.json
│ │ ├── src/
│ │ └── tests/
│ │
│ └── generated-types/ # ⚠️ READ-ONLY - Generated from Python
│ ├── package.json
│ └── src/ # TypeScript interfaces from Pydantic
│
├── farmer-pwa/ # 📱 Mobile-first PWA
│ ├── README.md
│ ├── docs/
│ ├── src/ # Dali writes here
│ ├── tests/ # Marie writes here
│ │ ├── unit/
│ │ └── component/
│ ├── public/
│ ├── Dockerfile
│ ├── package.json
│ └── vite.config.ts
│
└── stakeholder-portal/ # 🏢 Stakeholder web portal
├── README.md
├── docs/
├── src/
├── tests/
├── Dockerfile
└── package.json
Why _packages/ prefix?
- Sorts to top of directory listing
- Visually distinct from apps
- Signals "shared, don't deploy directly"
For Dali: Never leave apps/. All your shared UI code, utilities, and types are here in _packages/.
services/ - Python World (Backend)
The services/ folder is a pure Python workspace. It contains all backend microservices AND shared Python packages.
Domain concept: A domain is a bounded context - a group of related microservices that work together to serve a specific business capability.
Why domain grouping?
Microservices within a domain: - Share business concepts and vocabulary - Often communicate with each other - Can share a database (per-domain DB) - Are deployed to the same K8s namespace - Are owned by the same team (or agent)
services/ # 🔵 PYTHON WORLD (uv workspace)
├── README.md
├── docs/ # Shared backend patterns
├── pyproject.toml # Root workspace config
├── uv.lock
│
├── _packages/ # 📦 Shared Python Code
│ ├── common-utils/ # Logging, middleware, config
│ │ ├── pyproject.toml
│ │ ├── src/
│ │ └── tests/
│ │
│ └── shared-models/ # 🔑 SOURCE OF TRUTH - Pydantic Models
│ ├── pyproject.toml
│ ├── src/
│ │ ├── user.py # User model
│ │ ├── survey.py # Survey model
│ │ └── ...
│ └── tests/
│
├── user-management/ # 👤 USER DOMAIN
│ ├── README.md # Domain overview
│ ├── docs/ # Domain concepts, boundaries
│ │
│ ├── bff/ # GraphQL Backend-for-Frontend
│ │ ├── README.md
│ │ ├── docs/
│ │ ├── src/ # Dede writes here
│ │ ├── tests/ # Marie writes here
│ │ ├── Dockerfile
│ │ └── pyproject.toml
│ │
│ ├── auth-service/ # Authentication service
│ │ └── ...
│ │
│ └── profile-service/ # User profile management
│ └── ...
│
├── surveys/ # 📋 SURVEYS DOMAIN
│ ├── README.md
│ ├── docs/
│ ├── bff/
│ ├── survey-service/
│ └── analytics-service/
│
└── payments/ # 💰 PAYMENTS DOMAIN
├── README.md
├── docs/
├── bff/
└── payout-service/
Domain to K8s Namespace Mapping:
| Domain | Folder | K8s Namespace | Database |
|---|---|---|---|
| User Management | services/user-management/ |
user-management |
user_db |
| Surveys | services/surveys/ |
surveys |
surveys_db |
| Payments | services/payments/ |
payments |
payments_db |
For Dede: Never leave services/. All your shared utilities and models are here in _packages/.
The Shared Types Problem: Solved
You need User defined in Python (for the API) AND in TypeScript (for the frontend). Don't maintain two copies - they'll drift apart.
Solution: Generation
┌─────────────────────────────────────┐
│ services/_packages/shared-models/ │ 🔵 SOURCE OF TRUTH (Python)
│ └── src/user.py │ Pydantic models
└──────────────────┬──────────────────┘
│
│ .github/workflows/generate-types.yml
│ (CI runs on changes to shared-models/)
│
▼
┌─────────────────────────────────────┐
│ apps/_packages/generated-types/ │ 🟢 GENERATED (TypeScript)
│ └── src/user.ts │ TypeScript interfaces
└─────────────────────────────────────┘ ⚠️ READ-ONLY - Never edit!
How it works:
- Source of Truth: Dede defines Pydantic models in
services/_packages/shared-models/ - Generation: CI runs
datamodel-code-generatoror similar on changes - Output: TypeScript interfaces land in
apps/_packages/generated-types/ - Consumption: Dali imports from
@packages/generated-types
Critical rule: apps/_packages/generated-types/ is READ-ONLY. Dali can import from it but NEVER edit it. Changes must flow from Python → TypeScript.
platform/ - Platform Service Configurations
Configuration files for platform services, along with scripts to deploy them. These are the source of truth for config content, NOT the K8s deployment specs.
platform/
├── README.md
├── docs/
│
├── openfga/
│ ├── model.fga # Authorization model
│ ├── tuples/ # Authorization tuples
│ │ └── base.json
│ └── scripts/
│ ├── deploy-model.sh # Push model via API
│ └── seed-tuples.sh # Seed tuples for dev/demo
│
├── supertokens/
│ ├── config.yaml # Auth server config
│ └── scripts/
│ └── ...
│
├── temporal/
│ └── workflows/ # Workflow definitions
│
└── unleash/
├── flags.json # Feature flags
└── scripts/
└── deploy-flags.sh
| Path | What It Is | Deployed By |
|---|---|---|
openfga/model.fga |
Authorization model | platform/openfga/scripts/deploy-model.sh |
openfga/tuples/ |
Authorization tuples | platform/openfga/scripts/seed-tuples.sh |
supertokens/config.yaml |
Auth server config | Hydration → ArgoCD (as ConfigMap) |
temporal/workflows/ |
Workflow definitions | Your code imports directly |
unleash/flags.json |
Feature flags | platform/unleash/scripts/deploy-flags.sh |
Why co-locate scripts with configs? - Scripts deploy the configs they live next to - Gus doesn't need to jump between folders - Clear ownership: each platform service is self-contained
Why separate from infra/k8s/?
- platform/ = Business logic (WHAT the config says + HOW to push it)
- infra/k8s/ = Deployment specs (HOW it runs in K8s)
- Some configs are pushed via API, not mounted as ConfigMaps
infra/ - Infrastructure & Deployment
All infrastructure-as-code and deployment manifests.
infra/terraform/ - Cloud Infrastructure
Terraform for provisioning cloud resources (VPC, EKS, RDS, etc.).
infra/terraform/
├── modules/ # Reusable Terraform modules
│ ├── vpc/
│ ├── eks/
│ ├── rds/
│ └── redis/
└── environments/ # Per-environment root modules
├── dev/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── staging/
└── prod/
Key principle: Each environment has its own state, isolated from others.
infra/k8s/ - GitOps Manifests (ArgoCD)
Kubernetes manifests managed by ArgoCD using the 3-level pattern:
infra/k8s/
├── bootstrap/ # Level 1: ArgoCD itself
│ └── argocd/
│ └── install.yaml
│
├── appsets/ # Level 2: ApplicationSets (generates apps)
│ ├── apps.yaml # Creates apps for frontend apps
│ └── services.yaml # Creates apps for backend services
│
├── base/ # Level 3: Base K8s manifests (Kustomize)
│ ├── user-management/ # Maps to services/user-management/
│ │ ├── bff/
│ │ ├── auth-service/
│ │ └── profile-service/
│ ├── surveys/ # Maps to services/surveys/
│ │ └── ...
│ └── platform/ # Platform services
│ ├── openfga/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── kustomization.yaml
│ │ └── _generated/ # ⚠️ Hydrated - gitignored
│ │ └── model.fga
│ └── supertokens/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── kustomization.yaml
│ └── _generated/ # ⚠️ Hydrated - gitignored
│ └── config.yaml
│
└── overlays/ # Environment-specific patches
├── local/
├── dev/
├── staging/
└── prod/
Why 3-level pattern? 1. Bootstrap - ArgoCD manages itself 2. ApplicationSets - Dynamically generate apps from folder structure 3. Base + Overlays - Kustomize for DRY manifests
The Hydration Pattern: Copy, Don't Link
Problem: Relative paths like ../../../../platform/supertokens/config.yaml in Kustomize are:
- Fragile and break easily
- Confuse tools and AI agents
- Often blocked by Kustomize security settings
Solution: Use the Hydration Pattern - copy configs into infra/ before deployment.
┌─────────────────────────────────┐
│ platform/supertokens/ │ 📋 SOURCE OF TRUTH
│ └── config.yaml │ (Gus edits here)
└──────────────────┬──────────────┘
│
│ scripts/hydrate-infra.sh
│ (CI runs before Kustomize build)
│
▼
┌─────────────────────────────────────────────────┐
│ infra/k8s/base/platform/supertokens/_generated │ 🔧 HYDRATED COPY
│ └── config.yaml │ (gitignored)
└─────────────────────────────────────────────────┘
The hydration script:
#!/bin/bash
# scripts/hydrate-infra.sh
set -e
echo "💧 Hydrating infrastructure manifests..."
# SuperTokens config
echo " → SuperTokens..."
mkdir -p infra/k8s/base/platform/supertokens/_generated
cp platform/supertokens/config.yaml infra/k8s/base/platform/supertokens/_generated/config.yaml
# OpenFGA model (if needed as ConfigMap)
echo " → OpenFGA..."
mkdir -p infra/k8s/base/platform/openfga/_generated
cp platform/openfga/model.fga infra/k8s/base/platform/openfga/_generated/model.fga
echo "✅ Infrastructure hydration complete."
Kustomize now uses local files:
# infra/k8s/base/platform/supertokens/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: supertokens-config
files:
# Robust: local path, no relative path hell
- config.yaml=_generated/config.yaml
Why hydration is better for AI agents:
- Atomic units - When Gus looks at
infra/k8s/base/platform/supertokens/, he sees a complete, self-contained package - No path guessing - Gus doesn't need to mentally resolve
../../../../paths - Fast failure - If Veuve deletes a config in
platform/by mistake,hydrate-infra.shfails immediately with "File not found" - GitIgnored -
_generated/folders are not committed; they're build artifacts
Add to .gitignore:
# Hydrated configs (build artifacts)
**/_generated/
infra/docker/ - Local Development
Dockerfiles for local development dependencies (Postgres, Redis, etc.).
scripts/ - Developer Scripts (Root Level)
Generic development scripts that don't belong to any specific platform service.
scripts/
├── setup.sh # First-time dev setup
├── test-all.sh # Run all tests
├── reset-db.sh # Reset local databases
└── hydrate-infra.sh # Copy platform configs into infra/k8s
Why at root level?
- These are generic dev scripts, not tied to any platform service
- Easy to find: ./scripts/setup.sh
- hydrate-infra.sh bridges platform/ and infra/ - doesn't belong to either
Platform-specific scripts live with their configs:
- OpenFGA deploy: platform/openfga/scripts/deploy-model.sh
- Feature flags: platform/unleash/scripts/deploy-flags.sh
seed-data/ - Demo and Test Data
Seed data for demos and local development.
seed-data/
├── README.md
├── scenarios/
│ ├── demo/ # Demo data for sales
│ │ ├── users.json
│ │ ├── surveys.json
│ │ └── responses.json
│ └── cocoa-cooperative/ # Specific demo scenario
│ └── ...
└── seed.py # Seed script
Why at root level? - Seed data spans multiple domains (users, surveys, payments) - Used by both humans (demos) and tests (fixtures) - Clear purpose: demo/dev data, not production
tests/ - Cross-Cutting Tests
Tests that span multiple services or test infrastructure.
| Path | What It Tests | When It Runs |
|---|---|---|
e2e/ |
Full system, multiple services | PR merge, nightly |
contract/ |
API compatibility between services | Every commit |
journey/ |
User flows across the system | PR merge, nightly |
load/ |
Performance under stress | Weekly, before release |
smoke/ |
Quick "is everything up?" check | After deploy |
infra/terraform/ |
Terraform module validation | Every commit |
infra/k8s/ |
K8s manifest validation (kubeval) | Every commit |
infra/docker/ |
Dockerfile linting (hadolint) | Every commit |
Note: Unit and integration tests live WITH the code they test (in services/**/tests/, apps/**/tests/).
docs/ - Root Documentation
MkDocs aggregates documentation from all docs/ folders across the repo.
# mkdocs.yml
nav:
- Home: index.md
- Getting Started: getting-started/index.md
- Architecture: architecture/index.md
- Apps:
- Overview: apps/docs/index.md
- Farmer PWA: apps/farmer-pwa/docs/index.md
- Stakeholder Portal: apps/stakeholder-portal/docs/index.md
- Services:
- Overview: services/docs/index.md
- User Management:
- Overview: services/user-management/docs/index.md
- Auth Service: services/user-management/auth-service/docs/index.md
- Surveys:
- Overview: services/surveys/docs/index.md
- Platform: platform/docs/index.md
- Infrastructure: infra/docs/index.md
- Tests: tests/docs/index.md
Progressive disclosure: Each level has its own docs/ for context-specific documentation.
Complete Repository Structure
my-project/
│
├── apps/ # 🟢 TYPESCRIPT WORLD (pnpm workspace)
│ ├── README.md
│ ├── docs/
│ ├── package.json # Root workspace config
│ ├── pnpm-workspace.yaml
│ ├── turbo.json
│ ├── tsconfig.base.json
│ │
│ ├── _packages/ # 📦 Shared TypeScript Code
│ │ ├── ui-library/ # Shared React components
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ └── tests/ # Marie writes here
│ │ │
│ │ ├── ts-utils/ # Logging, formatting, helpers
│ │ │ ├── package.json
│ │ │ ├── src/
│ │ │ └── tests/
│ │ │
│ │ └── generated-types/ # ⚠️ READ-ONLY - From Python
│ │ ├── package.json
│ │ └── src/
│ │
│ ├── farmer-pwa/ # 📱 Mobile-first PWA
│ │ ├── README.md
│ │ ├── docs/
│ │ ├── src/ # Dali writes here
│ │ ├── tests/ # Marie writes here
│ │ │ ├── unit/
│ │ │ └── component/
│ │ ├── public/
│ │ ├── Dockerfile
│ │ ├── package.json
│ │ └── vite.config.ts
│ │
│ └── stakeholder-portal/ # 🏢 Stakeholder web portal
│ ├── README.md
│ ├── docs/
│ ├── src/
│ ├── tests/
│ ├── Dockerfile
│ └── package.json
│
├── services/ # 🔵 PYTHON WORLD (uv workspace)
│ ├── README.md
│ ├── docs/
│ ├── pyproject.toml # Root workspace config
│ ├── uv.lock
│ │
│ ├── _packages/ # 📦 Shared Python Code
│ │ ├── common-utils/ # Logging, middleware
│ │ │ ├── pyproject.toml
│ │ │ ├── src/
│ │ │ └── tests/ # Marie writes here
│ │ │
│ │ └── shared-models/ # 🔑 SOURCE OF TRUTH - Pydantic
│ │ ├── pyproject.toml
│ │ ├── src/
│ │ └── tests/
│ │
│ ├── user-management/ # 👤 User domain
│ │ ├── README.md
│ │ ├── docs/
│ │ │
│ │ ├── bff/ # GraphQL BFF
│ │ │ ├── README.md
│ │ │ ├── docs/
│ │ │ ├── src/ # Dede writes here
│ │ │ ├── tests/ # Marie writes here
│ │ │ ├── Dockerfile
│ │ │ └── pyproject.toml
│ │ │
│ │ ├── auth-service/
│ │ │ └── ...
│ │ │
│ │ └── profile-service/
│ │ └── ...
│ │
│ ├── surveys/ # 📋 Surveys domain
│ │ ├── README.md
│ │ ├── docs/
│ │ ├── bff/
│ │ ├── survey-service/
│ │ └── analytics-service/
│ │
│ └── payments/ # 💰 Payments domain
│ ├── README.md
│ ├── docs/
│ ├── bff/
│ └── payout-service/
│
├── platform/ # ⚙️ GUS'S DOMAIN - Platform Configs
│ ├── README.md
│ ├── docs/
│ │
│ ├── openfga/
│ │ ├── model.fga # Authorization model
│ │ ├── tuples/ # Authorization tuples
│ │ └── scripts/
│ │ ├── deploy-model.sh # Push model via API
│ │ └── seed-tuples.sh # Seed tuples
│ │
│ ├── supertokens/
│ │ ├── config.yaml # Auth server config
│ │ └── scripts/
│ │
│ ├── temporal/
│ │ └── workflows/ # Workflow definitions
│ │
│ └── unleash/
│ ├── flags.json # Feature flags
│ └── scripts/
│ └── deploy-flags.sh
│
├── infra/ # 🟠 GUS'S DOMAIN - Infrastructure
│ ├── README.md
│ ├── docs/
│ │
│ ├── terraform/
│ │ ├── README.md
│ │ ├── docs/
│ │ ├── modules/
│ │ │ ├── vpc/
│ │ │ ├── eks/
│ │ │ ├── rds/
│ │ │ └── redis/
│ │ └── environments/
│ │ ├── dev/
│ │ ├── staging/
│ │ └── prod/
│ │
│ ├── k8s/
│ │ ├── README.md
│ │ ├── docs/
│ │ ├── bootstrap/
│ │ │ └── argocd/
│ │ ├── appsets/
│ │ ├── base/
│ │ │ ├── user-management/
│ │ │ │ ├── bff/
│ │ │ │ ├── auth-service/
│ │ │ │ └── profile-service/
│ │ │ ├── surveys/
│ │ │ ├── payments/
│ │ │ └── platform/
│ │ │ ├── openfga/
│ │ │ │ ├── deployment.yaml
│ │ │ │ ├── service.yaml
│ │ │ │ ├── kustomization.yaml
│ │ │ │ └── _generated/ # ⚠️ Gitignored - hydrated
│ │ │ └── supertokens/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── kustomization.yaml
│ │ │ └── _generated/ # ⚠️ Gitignored - hydrated
│ │ └── overlays/
│ │ ├── local/
│ │ ├── dev/
│ │ ├── staging/
│ │ └── prod/
│ │
│ └── docker/
│ ├── postgres/
│ └── redis/
│
├── scripts/ # 🛠️ Generic dev scripts
│ ├── setup.sh # First-time setup
│ ├── test-all.sh # Run all tests
│ ├── reset-db.sh # Reset databases
│ └── hydrate-infra.sh # Copy platform configs → infra/k8s
│
├── seed-data/ # 🌱 Demo and test data
│ ├── README.md
│ ├── scenarios/
│ │ ├── demo/
│ │ └── cocoa-cooperative/
│ └── seed.py
│
├── tests/ # 🧪 MARIE'S DOMAIN - Cross-cutting
│ ├── README.md
│ ├── docs/
│ │
│ ├── e2e/
│ │ ├── conftest.py
│ │ └── test_user_registration.py
│ │
│ ├── contract/
│ │ ├── consumer/
│ │ └── provider/
│ │
│ ├── journey/
│ │ └── test_farmer_onboarding.py
│ │
│ ├── load/
│ │ ├── locustfile.py
│ │ └── scenarios/
│ │
│ ├── smoke/
│ │ └── test_all_services_up.py
│ │
│ └── infra/
│ ├── terraform/
│ ├── k8s/
│ └── docker/
│
├── specs/ # 📋 FEATURE SPECS (All agents)
│ ├── README.md
│ │
│ └── <issue-number>/
│ ├── product/ # Veuve writes here
│ ├── architecture/ # Duc writes here
│ ├── test-strategy.md # Marie writes here
│ ├── docs-review.md # Victor writes here
│ ├── review.md # General writes here
│ └── retro.md # Socrate writes here
│
├── docs/ # 📚 GLOBAL DOCS (Duc owns)
│ ├── index.md
│ ├── getting-started/
│ ├── architecture/
│ ├── user-journeys/
│ └── api/
│
├── .farmerspec/ # 🤖 AGENT SYSTEM
│ ├── state.yaml
│ ├── agents/
│ ├── templates/
│ └── retros/
│
├── .github/
│ ├── workflows/
│ │ ├── ci.yml
│ │ ├── generate-types.yml # Runs on Python model changes
│ │ ├── deploy-dev.yml
│ │ ├── deploy-staging.yml
│ │ └── deploy-prod.yml
│ └── CODEOWNERS
│
├── docker-compose.yml
├── docker-compose.demo.yml
├── Makefile
├── mkdocs.yml
├── CLAUDE.md
├── README.md
├── .env.example
└── .gitignore
Why This Structure Works for AI Agents
1. Pure Language Worlds
The most important optimization: language isolation.
apps/ → 100% TypeScript → Dali's world
services/ → 100% Python → Dede's world
Benefits:
- Dali never sees Python files
- Dede never sees TypeScript files
- No mixed-language packages/ folder
- Simpler linting, testing, and CI
2. Agents Never Leave Their World
| Agent | Stays Within |
|---|---|
| Dali | apps/ only |
| Dede | services/ only |
| Gus | infra/, platform/ |
| Marie | */tests/ and tests/ |
No context-switching between languages.
3. _packages/ is Co-located
Shared code lives WITH its language:
apps/
├── _packages/ # Shared TypeScript (Dali's)
│ ├── ui-library/
│ ├── ts-utils/
│ └── generated-types/
└── farmer-pwa/
services/
├── _packages/ # Shared Python (Dede's)
│ ├── common-utils/
│ └── shared-models/
└── user-management/
Why _packages/?
- Sorts to top (underscore prefix)
- Visually distinct from apps/services
- Signals "shared, don't deploy"
4. Type Generation Solves Cross-Language
The User model exists in both languages, but:
- Source of truth: services/_packages/shared-models/ (Python)
- Generated: apps/_packages/generated-types/ (TypeScript)
Dali reads from generated types but NEVER edits them. Changes flow one way.
5. Hydration Keeps K8s Self-Contained
The hydration pattern copies platform/ configs into infra/k8s/:
platform/supertokens/config.yaml
↓ scripts/hydrate-infra.sh
infra/k8s/base/platform/supertokens/_generated/config.yaml
Benefits for Gus:
- Each K8s manifest folder is self-contained
- No relative path hell (../../../../)
- Clear build step before deployment
- Fast failure if source config is missing
6. Platform Scripts Live With Configs
platform/openfga/
├── model.fga # The config
├── tuples/ # The data
└── scripts/
├── deploy-model.sh # How to deploy the config
└── seed-tuples.sh # How to seed the data
Gus doesn't need to jump between folders - everything is co-located.
7. Domain Grouping Limits Blast Radius
Services are grouped by domain:
services/
├── user-management/ # 👤 User domain
│ ├── bff/
│ ├── auth-service/
│ └── profile-service/
├── surveys/ # 📋 Surveys domain
└── payments/ # 💰 Payments domain
When Dede works on surveys/survey-service/, they don't accidentally touch payments/.
8. K8s Mirrors Services
services/user-management/auth-service/
↓
infra/k8s/base/user-management/auth-service/
Gus can map any service to its K8s manifests by path.
9. Progressive Documentation
Docs exist at every level:
docs/ # Global (Duc)
apps/
├── docs/ # All apps (Dali)
└── farmer-pwa/
└── docs/ # This app (Dali)
services/
├── docs/ # All services (Dede)
└── user-management/
├── docs/ # This domain (Dede)
└── auth-service/
└── docs/ # This service (Dede)
Victor knows exactly where to look and who owns each doc.
Agent Workflow Example
Feature: Add user authentication
-
Baron creates
specs/042-user-auth/, sets state toclassification -
Veuve writes
specs/042-user-auth/product/requirements.md -
Duc writes
specs/042-user-auth/architecture/design.md, updatesdocs/architecture/auth.md -
Marie writes tests:
services/user-management/auth-service/tests/unit/test_auth.pyservices/user-management/auth-service/tests/integration/test_auth_flow.pyapps/farmer-pwa/tests/component/test_login_form.tsxtests/e2e/test_login_journey.py-
tests/infra/k8s/test_auth_service_manifest.py -
Dede implements backend (never leaves
services/): services/user-management/auth-service/src/-
services/_packages/shared-models/src/user.py -
CI runs type generation (on change to shared-models):
- Input:
services/_packages/shared-models/ -
Output:
apps/_packages/generated-types/ -
Dali implements frontend (never leaves
apps/): apps/farmer-pwa/src/components/LoginForm.tsxapps/_packages/ui-library/src/AuthButton.tsx-
Imports from
@packages/generated-types -
Gus implements infrastructure:
platform/supertokens/config.yamlinfra/k8s/base/user-management/auth-service/-
Runs
scripts/hydrate-infra.shbefore testing -
Victor reviews docs, writes
specs/042-user-auth/docs-review.md -
General reviews code, creates PR
-
Socrate analyzes workflow, writes
specs/042-user-auth/retro.md
CI Pipeline
With language separation and hydration, CI becomes clear:
# .github/workflows/ci.yml
jobs:
python-tests:
if: contains(github.event.paths, 'services/')
steps:
- run: cd services && uv run pytest
typescript-tests:
if: contains(github.event.paths, 'apps/')
steps:
- run: cd apps && pnpm test
type-generation:
if: contains(github.event.paths, 'services/_packages/shared-models/')
steps:
- run: datamodel-codegen --input services/_packages/shared-models/src --output apps/_packages/generated-types/src
- run: git add apps/_packages/generated-types/
- run: git commit -m "chore: regenerate TypeScript types" || true
- run: git push
infra-validation:
if: contains(github.event.paths, 'infra/') || contains(github.event.paths, 'platform/')
steps:
# Hydrate before validation
- run: ./scripts/hydrate-infra.sh
- run: cd infra/terraform && terraform validate
- run: cd infra/k8s && kustomize build base/
Deploy workflow:
# .github/workflows/deploy-prod.yml
jobs:
deploy:
steps:
- uses: actions/checkout@v4
# Hydrate before building manifests
- name: Hydrate Infrastructure
run: ./scripts/hydrate-infra.sh
- name: Build Manifests
run: kustomize build infra/k8s/overlays/prod > deploy.yaml
- name: Deploy
run: kubectl apply -f deploy.yaml
Platform Configs: Who Deploys What?
| Config | Location | Deployed By | How |
|---|---|---|---|
| OpenFGA model | platform/openfga/model.fga |
CI script | platform/openfga/scripts/deploy-model.sh |
| OpenFGA tuples | platform/openfga/tuples/ |
CI or manual | platform/openfga/scripts/seed-tuples.sh |
| SuperTokens config | platform/supertokens/config.yaml |
ArgoCD | Hydration → ConfigMap |
| Temporal workflows | platform/temporal/workflows/ |
Your code | Python import |
| Feature flags | platform/unleash/flags.json |
CI script | platform/unleash/scripts/deploy-flags.sh |
| K8s manifests | infra/k8s/ |
ArgoCD | Kustomize |
| Cloud infra | infra/terraform/ |
Terraform | terraform apply |
| TypeScript types | apps/_packages/generated-types/ |
CI workflow | Auto-generated |
Testing Strategy by Agent
| Test Type | Location | Written By | Made Pass By |
|---|---|---|---|
| Backend unit | services/<domain>/<svc>/tests/unit/ |
Marie | Dede |
| Backend integration | services/<domain>/<svc>/tests/integration/ |
Marie | Dede |
| Frontend unit | apps/<app>/tests/unit/ |
Marie | Dali |
| Frontend component | apps/<app>/tests/component/ |
Marie | Dali |
| Shared Python tests | services/_packages/*/tests/ |
Marie | Dede |
| Shared TS tests | apps/_packages/*/tests/ |
Marie | Dali |
| E2E | tests/e2e/ |
Marie | All |
| Contract | tests/contract/ |
Marie | Dede/Dali |
| Journey | tests/journey/ |
Marie | All |
| Load | tests/load/ |
Marie | Gus validates |
| Smoke | tests/smoke/ |
Marie | Gus validates |
| Infra | tests/infra/ |
Marie | Gus |
Critical rule: Once Marie writes tests, they are IMMUTABLE. Implementation agents make them pass, they don't change them.
Summary: Why This Structure?
| Design Decision | Benefit for AI Agents |
|---|---|
Language isolation (apps/ vs services/) |
Agents never mix languages |
_packages/ inside each world |
Shared code is co-located, not separate |
| Generated types (Python → TS) | Single source of truth, one-way flow |
| Hydration pattern for K8s | Self-contained manifests, no relative paths |
No root packages/ folder |
Forces language purity |
| Domain-grouped services | Clear boundaries, limited blast radius |
Multiple apps in apps/ |
Each app independent, Dali focuses on one |
| Platform scripts with configs | Gus doesn't jump between folders |
| K8s structure mirrors services | Easy mental mapping for Gus |
_generated/ folders gitignored |
Build artifacts, not source code |
| Progressive docs at every level | Victor can review systematically |
specs/ as feature home |
All agents contribute to single location |
| Tests co-located with code | Agents see what to implement and test together |
This structure is designed for AI agents to operate efficiently with strict language separation, minimal context switching, and clear ownership boundaries.