Table of Contents
In the rapid evolution of Artificial Intelligence-assisted software development, we have witnessed the massive adoption of standards like agents.md or rules.md. These files have become the backbone for guiding AI agents (such as GitHub Copilot, Cursor, Gemini, or Claude) in aspects of architecture, code patterns, testing strategies, and naming conventions.
As we delve into 2025, the way we interact with code has radically changed. We no longer start with a blank canvas; we start with highly structured context. However, this context can be a double-edged sword. If an AI agent does not know the specific conventions of your project, your tech stack, or your architectural preferences in complex ecosystems like Android, the result is often generic, outdated code, or worse, code that introduces subtle but destructive anti-patterns.
Every new chat session with an LLM used to feel like onboarding a new junior developer from scratch every single day: “We use Kotlin, we prefer dependency injection with Hilt, don’t use findViewById, use Jetpack Compose for the UI, make sure to handle asynchronous flows with StateFlow and isolate domain logic in UseCases.” Repeating this mantra over and over resulted in a massive waste of time and palpable frustration for the senior developer. The definitive solution to this problem of chronic agent “amnesia” was the birth and standardization of persistent rule files.
The rules.md standard (and its platform-specific incarnations like .cursorrules, CLAUDE.md, or modern structured rules in the .cursor/rules/ directory with the powerful .mdc format) acts as a binding semantic contract between the human developer and the synthetic agent. It is the equivalent of an automated, deterministic, and permanent onboarding process. By injecting these rules contextually and surgically into every interaction, the AI ceases to be a stochastic text generator prone to hallucinations and becomes a highly specialized team member, perfectly aligned with the project’s technical vision and domain constraints.
In this article, we will explore in depth the concept of the rules standard, its fascinating history, how it has evolved from monolithic plain text files into granular and dynamic rule systems, and how to masterfully implement these guidelines in complex Android projects using Kotlin. Throughout this extensive technical guide, you will not only learn the theoretical foundations, but you will also obtain practical templates, configuration architectures, and advanced strategies to master AI-assisted development, ensuring your code scales without compromising quality or security.
🏛️ Origins and History: From Chaos to Systemic Order
The concept of a file dedicated to dictating behavioral rules for AI did not emerge overnight in an aseptic research lab, nor was it imposed by a single corporate standardizing committee. Rather, its development was an organic, iterative, and community-driven response, fueled by the intense friction engineering teams experienced when interacting with generative models in production environments.
The Era of Agentic Prehistory (2023 - Early 2024)
In the early days of tools like the original GitHub Copilot or ChatGPT embedded in the workflow, the interaction was purely transactional. Developers copied code blocks, pasted them into a web chat window, and added ad-hoc instructions: “Refactor this using Kotlin Coroutines.” The AI had no idea what version of Kotlin you were using, what dependencies you had in your build.gradle.kts, or what the general architecture of your Android application was.
This lack of environmental awareness required exhausting mental effort from the developer to provide the necessary context in every prompt. We called this the era of “Fatigue Prompting.”
The Rise of Global Prompts and .cursorrules (Late 2024 - 2025)
With the arrival of AI-native IDEs like Cursor, Windsurf, and advanced VS Code extensions, the paradigm shifted. These tools introduced the ability to read the context of the local repository. However, to prevent the AI from guessing the team’s intentions by heuristically analyzing the codebase, the concept of a global manifest file was introduced.
Cursor popularized the .cursorrules file. Located in the project root, this plain text file (usually in Markdown format) served as a “System Prompt” invisibly injected at the beginning of every interaction with the AI model. Teams started dumping all their tribal wisdom into this file: naming conventions, warnings about deprecated libraries, strict rules on error handling, and more.
The problem quickly arose: monoliths collapse under their own weight. A .cursorrules file for an enterprise Android application could easily exceed 1000 lines. It contained rules for the network layer (Retrofit/Ktor), rules for the UI (Jetpack Compose), database rules (Room), and CI/CD rules (GitHub Actions).
The “Token Tax” Problem and Context Dilution
When an LLM (like Claude 3.5 Sonnet or GPT-4o) receives a massive context, it suffers from two phenomena:
- Lost in the Middle: The model pays attention to the beginning and end of the prompt, but often ignores instructions located in the center of the document.
- Token Tax: Injecting 10,000 tokens of global rules into every small autocomplete request is extremely financially costly and adds latency (Time to First Token, TTFT). Why does the AI need to read the Firebase Crashlytics configuration rules when you are only asking it to center text on a Jetpack Compose button?
The Modular Revolution: The .mdc Format and .cursor/rules/ (2025)
To solve context dilution and the token tax, the industry evolved toward a context-routing rule system. Instead of a single .cursorrules file, modern projects adopted the .cursor/rules/ directory (or analogous implementations in other tools), populated by multiple files with the .mdc (Markdown Context) extension or simply .md files with structured frontmatter.
This breakthrough allowed what we call Dynamic Rule Injection. Each rule now has metadata (usually in YAML frontmatter) defining “when” and “where” it should be applied. If the developer opens a file ending in ViewModel.kt, the tool automatically injects state management rules but completely omits visual design rules.
This modularity not only saved millions of dollars in inference costs globally, but it also drastically increased the accuracy and rule adherence of the agents, enabling the creation of ultra-cohesive and secure codebases.
⚖️ The Rules Ecosystem: Comparison and Nomenclature
In 2025, AI coding agents have converged on a common architectural pattern (Markdown files located at the repository root), but they use different names and conventions depending on the vendor. Understanding this ecosystem is vital for teams operating in agnostic environments or migrating between tools.
1. agents.md (The Open Standard)
Adopted by the Linux Foundation and supported by over 100,000 open-source repositories, agents.md is the equivalent of a robots.txt file but for software agents. It is tool-agnostic. It was designed so that autonomous orchestration agents, CLI tools (like Aider), and CI/CD sub-agents understand the project context. They are typically files without complex metadata, focused on high-level architecture and engineering rules.
2. .cursorrules (The Historical Legacy)
The original single-file format. Although still supported for backward compatibility by Cursor, it is considered a deprecated pattern for medium to large projects. It remains useful for toy projects or single-purpose repositories (like an isolated Python script) where the overhead of creating a rules directory is unjustified.
3. .cursor/rules/*.mdc (The Industry Standard for Cursor)
The modern and recommended format. It uses .mdc files combining Markdown with YAML metadata. It allows routing based on “globs” (file path regular expressions). Its main advantage is dynamic context resolution, making it the most powerful option for multi-layered architectures like those found in the Android ecosystem (Clean Architecture with Presentation, Domain, and Data layers).
4. CLAUDE.md (Anthropic’s Approach)
Anthropic introduced CLAUDE.md for its Claude Code CLI tool and Claude Projects web interface. Unlike Cursor’s granular rules, CLAUDE.md leans towards a more holistic approach. Claude, thanks to its massive context windows (over 200k tokens) and legendary resistance to the “Lost in the Middle” problem, can process a very extensive CLAUDE.md without significant performance degradation. It is ideal for documenting the general project philosophy and frequent build commands (pnpm test, ./gradlew assembleDebug).
5. copilot-instructions.md (GitHub’s Vision)
GitHub Copilot uses this file to anchor the assistant’s instructions in the repository context. Its functionality aligns more closely with the old .cursorrules, applying instructions quite globally. However, Microsoft has been integrating Semantic Code Search capabilities, allowing Copilot to infer implicit rules by searching other project files.
The Intersection with design.md
It is crucial to distinguish between engineering rules and design guidelines. In the modern taxonomy of AI-assisted documentation:
agents.mdor.cursor/rules/: Dictate HOW software is built. They cover concurrency, static typing, architectures (MVVM, MVI), injection patterns, and security (avoiding SQL injection, handling sensitive data).design.md: Dictates the WHAT and WHY from a visual and user perspective. It covers color tokens (Material 3), typography, visual hierarchy, accessibility guidelines (TalkBack on Android), animations, and the application’s tone of voice.
An advanced frontend agent (like Spec-Kitty or OpenSpec) will simultaneously read a rule from compose.mdc (telling it to use Modifier.fillMaxWidth()) and the design.md file (telling it primary buttons must have a corner radius of 16.dp).
🧠 The Structural Anatomy of a Perfect .mdc File
To fully leverage contextual routing capabilities in 2025, we must understand the deep anatomy of a modern .mdc file. Designing rules is not simply writing a wishlist; it is Prompt Engineering applied at the infrastructure level.
A well-designed .mdc file consists of two fundamental parts: the YAML Frontmatter and the Markdown Body.
1. The YAML Frontmatter: The Routing Brain
The frontmatter tells the IDE engine or agent orchestrator exactly when this rule is relevant. Without this, we revert to the dark ages of context dilution.
---
description: "Strict conventions for UI development with Jetpack Compose and Material 3"
globs: ["**/ui/**/*.kt", "**/components/**/*.kt", "**/screens/**/*.kt"]
alwaysApply: false
---
Let’s break down the components:
description: It’s not just a human comment. The agent’s semantic engine uses this description to decide whether to invoke the rule when the user asks a general question, even if they haven’t opened any specific file. If the user types “How do I make a button in this app?”, the agent scans all rule descriptions, finds “UI development,” and triggers this rule dynamically.globs: Defines file matching patterns. This is the magic. By specifying**/ui/**/*.kt, we guarantee these Jetpack Compose rules will never pollute the context when the agent is working on a Room database DAO (**/data/local/**/*.kt), saving tokens and preventing hallucinations.alwaysApply: A critical boolean. If set totrue, the rule is injected unconditionally into every prompt in this repository, regardless of open files. Use with extreme caution. ReservealwaysApply: trueexclusively for inviolable baseline rules (e.g., “Strictly respect SOLID principles,” “Always write in English”).
2. The Markdown Body: Clear, Concise, and Deterministic Directives
The rule body should avoid ambiguous language. LLMs respond best to strong imperatives, clear negative constraints (“NEVER DO X”), and positive code examples (few-shot prompting).
Principles for writing the body:
- Priority by Negation: Language models often “forget” complex positive instructions but are remarkably obedient to absolute negative constraints. Always start with what NOT to do.
- Visual Hierarchy: Use headers (
##,###), bulleted lists, and bold text to highlight critical keywords (**MANDATORY**,**NEVER**). - Contextual Code Snippets: The fastest way to train an LLM on desired behavior is showing it an example of “Good Code” vs “Bad Code”.
Let’s look at a practical example of the internal structure of a rule for an Android ViewModel:
# ViewModel Implementation Rules
You are an expert Senior Android developer specializing in MVVM and Kotlin Coroutines.
Your goal is to write safe, reactive, and efficient ViewModels.
## 🚫 CRITICAL CONSTRAINTS (NEVER DO THIS)
- NEVER expose `MutableStateFlow` or `MutableLiveData` to the outside. Always use read-only properties (`StateFlow` or `LiveData`).
- NEVER pass the Android `Context` to a ViewModel. This causes massive memory leaks. Use dependency injection to provide resources if absolutely necessary, or handle strings in the UI.
- NEVER use `GlobalScope` or `CoroutineScope(Dispatchers.IO)`. ALWAYS use `viewModelScope.launch`.
## ✅ REQUIRED PATTERNS (ALWAYS DO THIS)
- Manage UI state using a single `UiState` data class.
- Employ a `Channel` event pipeline for *One-Time Events* (e.g., showing a Toast, navigation).
- Inject all dependencies (UseCases, Repositories) via constructor using the `@HiltViewModel` and `@Inject` annotations.
## 📝 CANONICAL REFERENCE EXAMPLE
```kotlin
// GOOD: State and event encapsulation with Hilt
@HiltViewModel
class UserProfileViewModel @Inject constructor(
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow(UserProfileUiState())
val uiState: StateFlow<UserProfileUiState> = _uiState.asStateFlow()
private val _uiEvent = Channel<UserProfileEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
fun fetchUser(userId: String) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val user = getUserProfileUseCase(userId)
_uiState.update { it.copy(isLoading = false, user = user) }
} catch (e: Exception) {
_uiState.update { it.copy(isLoading = false, error = e.message) }
_uiEvent.send(UserProfileEvent.ShowError(e.message ?: "Unknown error"))
}
}
}
}
```
This approach transforms your AI from a generic coder to an MVVM purist aligned with your exact architectural standards.
🛠️ Step-by-Step Guide: Implementing an .mdc System in a Complex Android Project
Imagine a large-scale Android project, perhaps a banking app or robust e-commerce platform, modularized by features (features/) and layers (core/, domain/, data/, ui/). Here is how we would architect the .cursor/rules/ directory for unprecedented AI control.
Step 1: Initialize the Directory and Base Rule
First, create the .cursor/rules/ directory at the root of your project. The first file we should add is the base rule that must always apply.
Create .cursor/rules/00-base-android.mdc:
---
description: "Fundamental Android rules applicable to the entire project. Style, Kotlin, and base architecture."
globs: ["*.kt", "*.kts"]
alwaysApply: true
---
In the body of this file, we define universal things:
- Use Kotlin 2.1+.
- Prevent the use of
!!(force non-null operator). The AI must prefer the safe call?.and the Elvis operator?:. - Require standardized naming conventions (interfaces start with “I” or implemented classes end in “Impl”).
- Dictate that all dates must be handled with
java.timeand never with the legacyjava.util.Date.
Step 2: Specific Rules for the Data Layer (Room & Retrofit)
The data layer requires strict rules regarding transaction handling, database migrations, and JSON serialization.
Create .cursor/rules/10-data-layer.mdc:
---
description: "Rules for Repositories, DAOs (Room), DTOs, and network calls (Retrofit/Ktor)."
globs: ["**/data/**/*.kt", "**/*Dao.kt", "**/*Dto.kt", "**/*RepositoryImpl.kt"]
alwaysApply: false
---
The body should include instructions like:
- “All methods in Room DAOs must be
suspendfunctions unless they return aFlow.” - “NEVER expose database classes (Room Entities) or network models (DTOs) to the domain layer. ALWAYS map entities to clean Domain Models using
.toDomain()extension functions.” - “Use Moshi or Kotlinx Serialization. Do not use Gson under any circumstances; it is considered legacy in this project.”
Step 3: UI Layer and Jetpack Compose Rules
This is where the AI tends to derail the most due to the rapid evolution of the Compose ecosystem.
Create .cursor/rules/20-compose-ui.mdc:
---
description: "Development guidelines for UI components in Jetpack Compose, Material Design 3, and state management."
globs: ["**/ui/**/*.kt", "**/presentation/**/*.kt", "**/screens/**/*.kt", "**/components/**/*.kt"]
alwaysApply: false
---
Compose rules should be highly detailed:
- “All custom UI components must be
@Composablefunctions and must accept amodifier: Modifier = Modifierparameter as their first optional parameter.” - “Do not pass ViewModels down to child components (State Hoisting). Pass lambda functions for events (e.g.,
onItemClick: (Item) -> Unit) and explicitly required states.” - “Use
rememberSaveablefor UI states that must survive configuration changes, not justremember.” - “Integrate accessibility descriptions using
semantics { contentDescription = "..." }on interactive elements lacking inherent text.”
Step 4: Build Configuration and CI/CD Rules
The build environment also needs guidelines, especially when migrating to Gradle Version Catalogs or Kotlin DSL.
Create .cursor/rules/30-build-gradle.mdc:
---
description: "Gradle dependency management, Kotlin DSL, and Version Catalogs."
globs: ["*.gradle.kts", "gradle/libs.versions.toml", "build.gradle.kts"]
alwaysApply: false
---
Instructions:
- “Any new dependency must be added centrally in
libs.versions.toml. NEVER hardcode the version directly in abuild.gradle.kts.” - “Keep plugin blocks sorted alphabetically.”
By modularizing these instructions, we guarantee that when a developer asks the AI: “Add a button on the Profile screen,” the agent will only read 00-base-android.mdc and 20-compose-ui.mdc. It will not waste vital tokens (or algorithmic attention time) reading about database mappings or Gradle configurations.
🌐 Evolutionary Comparison: LLM Model Interaction with rules.md
The AI ecosystem is not homogeneous. Different model families process rule files with distinct strengths and weaknesses. As software engineers, we must optimize our .mdc files keeping the underlying hardware and the neural architecture of the agents in mind.
1. The Claude 3.5 Family (Anthropic) and the Perceptive Context Window
Claude 3.5 (including Sonnet and Opus) currently dominates expansive context understanding thanks to its hierarchical memory technology and sparse attention. When an .mdc file is injected into Claude, the model exhibits exceptional rule retention, even those located at the end of the document.
Recommended Tactic: With Claude, you can afford to be exhaustive. You can include multiple detailed “negative examples”. Claude excels at holistic refactoring tasks where it must scan an old file and align it with a lengthy style manual defined in rules.md. It is the preferred engine behind orchestrator agents (MAO) operating autonomously on entire PRs.
2. The GPT-4o / O1 Family (OpenAI) and Deductive Reasoning
O1 models introduced implicit reasoning paradigms (Hidden Chain of Thought). When faced with a rules file, these models don’t just read the guidelines; they “think” through them. If a rule in .cursorrules states “Dependency injection is forbidden in Composable components,” the O1 model will proactively infer the ramifications of this and structure the ViewModel to manage dependencies internally, exposing only simple lambdas to the UI.
Recommended Tactic: Be explicit about the “Whys.” O1 responds brilliantly if you explain the reasoning behind the rule. Instead of saying “Don’t use LiveData,” say “Don’t use LiveData because it doesn’t scale well with high-performance asynchronous Coroutines and violates our asynchronous reactive patterns.” The model will internalize the principle and apply it to edge cases not explicitly listed.
3. Lightweight Autocomplete Models (DeepSeek Coder V2, StarCoder 2, Gemini Nano)
These SLM (Small Language Models) operate locally on-device or via ultra-low latency APIs. Their job is to guess the next 10-50 lines of code as you type (inline tab-to-complete). They are highly sensitive to token saturation.
Recommended Tactic: For tools feeding rules to SLMs, granularity is life or death. Use the .cursor/rules/ directory strictly. If you inject more than 1000 tokens into an SLM, its autocomplete performance will collapse, and it will start generating random code or enter hallucination loops. Keep autocomplete .mdc files under 500 words.
🚀 Advanced Orchestration: Synergy Between rules.md, SDD, and MAO
The true power of rules.md emerges when integrated into higher-order development frameworks like Spec-Driven Development (SDD) or managed via a Multi-Agent Orchestrator (MAO).
In 2025, teams highly leveraged on AI do not rely on individual agents acting in a vacuum. They use orchestrators. When an MAO Orchestrator receives a new task (e.g., “Implement offline login with Room”), it follows a strict protocol:
- Rule Ingestion Phase: The orchestrator scans the repository and reads all policies in
.cursor/rules/. It compiles a “Project Constraints Profile”. - Specification Generation (SDD): Using a framework like OpenSpec, an Analyst sub-agent drafts a formal specification of the solution. This specification is automatically verified (using Socratic refutation techniques) against the rules extracted in phase 1. If the specification suggests using
SQLiteOpenHelper, the10-data-layer.mdcrule demanding the exclusive use ofRoomwill trigger an alarm, and the specification document will be rejected and re-generated before writing a single line of code. - Execution Phase: Multiple agents (frontend, backend, QA) proceed to write the code. Each agent dynamically receives only the subset of
.mdcfiles relevant to its domain. The QA Agent receives testing rules (40-testing.mdc) requiring it to useMockKandTurbineto test flows, ignoring the existence of Mockito.
This architecture guarantees that technical debt is prevented by design, not via tedious post-fact code reviews. Rules act as unyielding guardians of the repository, immunizing the project against AI hallucinations and stylistic divergences.
🛠️ Anti-patterns and Debugging: When Rules Fail
Even with the best .mdc system, things can go wrong. Here we list the most common anti-patterns we observe in modern teams and how to fix them.
1. The “Contradictory Rule” Anti-pattern
Symptom: The agent generates a block of code and then, in the same response, generates a comment block apologizing and rewriting the code, or produces a strange hybrid (e.g., mixing LiveData and StateFlow).
Cause: A precedence conflict exists. You might have a legacy global .cursorrules file saying “Use LiveData” and a new .cursor/rules/20-compose.mdc saying “Use StateFlow.” The LLM is confused by the double bind.
Solution: Rule audit. Eliminate global .cursorrules and migrate 100% of the logic to .mdc files routed by mutually exclusive globs.
2. The “Abstraction Overload” Anti-pattern
Symptom: The model completely ignores rules, reverting to generating generic code.
Cause: You have exceeded the effective context window or diluted the model’s attention with irrelevant information in an alwaysApply: true block.
Solution: Refactor your rules. Move specialized logic to .mdc files with stricter globs. Replace long narrative paragraphs with concise bullet points.
3. The “Utopian Perfection Rule” Anti-pattern
Symptom: Rules demand such elaborate architectures (Clean Architecture with 5 layers of empty interfaces) that the AI fails to generate simple functional code.
Cause: Rules disconnected from pragmatism. Demanding excessive abstractions chokes the agent’s generative capabilities.
Solution: Implement pragmatic rules. Allow explicit exceptions. In your .mdc, you can add: “For simple CRUD operations without business logic, the UI is permitted to interact directly with the Repository, skipping the UseCases layer to avoid unnecessary boilerplate.”
🏆 Conclusion: The Future of AI Rules
The rules.md standard and the evolution towards the contextual .mdc format represent one of the most profound advancements in Human-Computer Interaction within software engineering. We have transitioned from assisted coding based on pre-trained general model knowledge to surgical development anchored in dynamic hyper-context.
In mobile app development with Kotlin and Android, where architectural fragmentation and the rapid evolution of libraries like Jetpack Compose can overwhelm even the most experienced teams, having a well-maintained .cursor/rules/ directory is not a luxury: it is an indispensable operational requirement in 2025.
Rules are the crystallization of human architect experience. They allow us to delegate low-level implementation to synthetic agents with total confidence, knowing they will respect our quality, security, and architectural design standards. The future of development will not belong to those who write code faster, but to those who know how to specify system constraints with greater algorithmic precision. A great rule system transforms AI from a simple predictive tool into a truly aligned extension of the engineering team’s intellect.
You might also be interested in
Socratic Method Prompts: Breaking AI Sycophancy in Kotlin & Android Development
Learn how to stop LLMs from being compliant assistants and turn them into ruthless evaluators. Discover the mathematical anatomy of Socratic prompts for Android architecture, Kotlin Coroutines, and strict Spec-Driven Development.
design.md: The New Standard for AI-Assisted UI/UX Design
Discover how design.md complements agents.md, focusing on design decisions, UI/UX, accessibility, and Jetpack Compose for Android development in the era of AI agents.
Google Stitch: The AI-Powered UI Design Revolution in Android
Discover how Google Stitch, powered by Gemini, is transforming UI design in Android. Examples, guides, tricks, and its integration with Jetpack Compose and Kotlin.