Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions docs/ai_builder/integrations/agents_md.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ The [reflex-dev/agent-skills](https://github.com/reflex-dev/agent-skills) reposi

## Installation

Download the template into your project root, next to `rxconfig.py`:
`reflex init` writes a starter `AGENTS.md` into the project root alongside `rxconfig.py` by default (pass `--no-agents` to opt out):

```bash
reflex init
```

The Reflex-provided content sits between `reflex managed` begin/end markers. Anything you add outside the markers is preserved when init refreshes the managed section, so it is safe to re-run. If a pre-existing `AGENTS.md` has no markers, the managed section is added at the top and your content is kept below it. For Claude Code users, init also creates a `CLAUDE.md` importing `AGENTS.md` (or updates an existing one that doesn't reference it).

To add the template to an existing project, download it into the project root, next to `rxconfig.py`:

```bash
curl -fsSL https://raw.githubusercontent.com/reflex-dev/agent-skills/main/AGENTS.md -o AGENTS.md
Expand Down Expand Up @@ -63,7 +71,9 @@ Keep entries short and imperative — assistants follow concise, direct instruct

## Keeping Files Updated

Reflex evolves quickly. If you used `curl` to download the template, re-run the same command to refresh it:
Reflex evolves quickly. If `reflex init` created your `AGENTS.md`, re-running `reflex init` refreshes the content between the managed markers while preserving everything you added outside them.

If you used `curl` to download the template, re-run the same command to refresh it (note this replaces the whole file):

```bash
curl -fsSL https://raw.githubusercontent.com/reflex-dev/agent-skills/main/AGENTS.md -o AGENTS.md
Expand Down
33 changes: 33 additions & 0 deletions docs/getting_started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@

~3 minutes · Requires Python 3.10+.

```md alert info
# Reading this as an AI agent or coding assistant?

You're likely here to build a Reflex app for someone. Reflex does **not** behave like the Flask/FastAPI/React code in your training data — read this before writing any code.

**Get the current docs (don't rely on training data — the API changes between versions):**

- Fetch [`https://reflex.dev/llms.txt`](https://reflex.dev/llms.txt) for a token-efficient map of the docs and core concepts.
- Use the [Reflex MCP server](/docs/ai/integrations/mcp-overview/) (`https://build.reflex.dev/mcp`) for live component/prop lookup so you don't hallucinate APIs. *(MCP access is an enterprise feature.)*
- Run `uv run reflex --version` and trust the live docs for that version over memory.

**Drop a rules file in the project root** (`AGENTS.md`, or `CLAUDE.md`/`.cursorrules` for your tool) so these conventions persist across your session — `reflex init` writes a starter `AGENTS.md` for you by default, and a minimal fallback is below.
```

Starter rules file:

```text
# Reflex conventions

- Reflex is pure Python that compiles to a React frontend. Do NOT write JS, HTML, or JSX.
- Components are function calls that return components; pass props as keyword args.
- NEVER use plain Python control flow on state Vars inside the render tree.
No `if`, `for`, `len()`, or f-strings over a Var — use `rx.cond`, `rx.foreach`,
and Var operators instead. (This is the most common mistake.)
- State lives in `rx.State` subclasses. State only mutates inside event-handler
methods — never at module load or render time. Derived values use `@rx.var`.
- Event handlers may be `async` and may `yield` to push intermediate UI updates.
- Always run commands with `uv run` (e.g. `uv run reflex run`). Never bare `python`.
- `.web/` is generated output — never edit or commit it. `rxconfig.py` is the config entry point.
- Verify your work headlessly: `CI=1 uv run reflex run` (frontend :3000, backend :8000),
add `--loglevel debug` to diagnose failures.
```

## Virtual Environment

We recommend [uv](https://docs.astral.sh/uv/) as the default; [venv](https://docs.python.org/3/library/venv.html), [conda](https://conda.io/), and [poetry](https://python-poetry.org/) are alternatives.
Expand Down
1 change: 1 addition & 0 deletions news/6620.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`reflex init` now writes a Reflex-managed section into `AGENTS.md` (fetched from the canonical source and delimited by markers that preserve surrounding user content), and bridges it for Claude Code by creating a `CLAUDE.md` importing `@AGENTS.md` — or, if a `CLAUDE.md` exists without the import, managing the section there directly.
1 change: 1 addition & 0 deletions packages/reflex-base/news/6620.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `AgentsMd` constants (canonical URL, managed-section markers, and `CLAUDE.md` bridge) supporting `reflex init` AGENTS.md generation.
2 changes: 2 additions & 0 deletions packages/reflex-base/src/reflex_base/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
)
from .config import (
ALEMBIC_CONFIG,
AgentsMd,
Config,
DefaultPorts,
Expiration,
Expand Down Expand Up @@ -82,6 +83,7 @@
"ROUTE_NOT_FOUND",
"SESSION_STORAGE",
"SETTER_PREFIX",
"AgentsMd",
"Bun",
"ColorMode",
"CompileContext",
Expand Down
16 changes: 16 additions & 0 deletions packages/reflex-base/src/reflex_base/constants/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ class GitIgnore(SimpleNamespace):
}


class AgentsMd(SimpleNamespace):
"""AGENTS.md constants."""

# The AGENTS.md file written to the app root.
FILE = Path("AGENTS.md")
# The CLAUDE.md bridge file; Claude Code reads CLAUDE.md rather than AGENTS.md.
CLAUDE_FILE = Path("CLAUDE.md")
# Import line that pulls AGENTS.md into CLAUDE.md.
CLAUDE_IMPORT = "@AGENTS.md"
# The canonical AGENTS.md maintained in the reflex-dev/agent-skills repo.
CANONICAL_URL = "https://raw.githubusercontent.com/reflex-dev/agent-skills/aditya/agentsmd/AGENTS.md"
# Markers delimiting the Reflex-managed section; user content outside them is preserved.
BEGIN_MARKER = "<!-- reflex managed begin (do not edit inside this block; add custom content outside the markers) -->"
END_MARKER = "<!-- reflex managed end -->"


class PyprojectToml(SimpleNamespace):
"""Pyproject.toml constants."""

Expand Down
13 changes: 12 additions & 1 deletion reflex/reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def _init(
name: str,
template: str | None = None,
ai: bool = False,
agents: bool = False,
):
"""Initialize a new Reflex app in the given directory."""
from reflex.utils import exec, frontend_skeleton, prerequisites, templates
Expand Down Expand Up @@ -89,6 +90,10 @@ def _init(
# Initialize the .gitignore.
frontend_skeleton.initialize_gitignore()

# Write or refresh the AGENTS.md for AI coding agents.
if agents:
frontend_skeleton.initialize_agents_md()

template_msg = f" using the {template} template" if template else ""
if Path(constants.PyprojectToml.FILE).exists():
needs_user_manual_update = False
Expand Down Expand Up @@ -122,13 +127,19 @@ def _init(
is_flag=True,
help="Use AI to create the initial template. Cannot be used with existing app or `--template` option.",
)
@click.option(
"--agents/--no-agents",
default=True,
help="Write an AGENTS.md to guide AI coding agents working in the app (enabled by default).",
)
Comment thread
amsraman marked this conversation as resolved.
def init(
name: str,
template: str | None,
ai: bool,
agents: bool,
):
"""Initialize a new Reflex app in the current directory."""
_init(name, template, ai)
_init(name, template, ai, agents)


def _compile_app(*, avoid_dirty_check: bool = True):
Expand Down
116 changes: 115 additions & 1 deletion reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import random
from pathlib import Path
from typing import Literal

from reflex_base import constants
from reflex_base.config import Config, get_config
Expand All @@ -11,7 +12,7 @@

from reflex.compiler import templates
from reflex.compiler.utils import write_file
from reflex.utils import console, path_ops
from reflex.utils import console, net, path_ops
from reflex.utils.prerequisites import get_project_hash, get_web_dir
from reflex.utils.registry import get_npm_registry

Expand Down Expand Up @@ -43,6 +44,119 @@ def initialize_gitignore(
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")


AgentsMdAction = Literal["managed", "bridge"]


def _plan_agents_md(
agents_file: Path, claude_file: Path
) -> list[tuple[Path, AgentsMdAction]]:
"""Decide which files receive the managed section or the CLAUDE.md bridge.

Claude Code reads CLAUDE.md rather than AGENTS.md, so: if CLAUDE.md is
missing, AGENTS.md is managed and a bridge importing it is planned; if
CLAUDE.md is the same file as AGENTS.md (symlink) or already imports it,
only AGENTS.md is managed; otherwise CLAUDE.md is managed directly, along
with AGENTS.md if it already exists.

Args:
agents_file: The AGENTS.md file in the app root.
claude_file: The CLAUDE.md file in the app root.

Returns:
(file, action) pairs to apply in order.
"""
if not claude_file.exists():
plan: list[tuple[Path, AgentsMdAction]] = [(agents_file, "managed")]
# A broken symlink gets no bridge; writing it would create the target.
if not claude_file.is_symlink():
plan.append((claude_file, "bridge"))
return plan
if (
agents_file.exists() and claude_file.samefile(agents_file)
) or constants.AgentsMd.CLAUDE_IMPORT in claude_file.read_text():
return [(agents_file, "managed")]
if agents_file.exists():
return [(agents_file, "managed"), (claude_file, "managed")]
return [(claude_file, "managed")]


def _apply_agents_md_action(
file: Path, action: AgentsMdAction, managed_agents_md_text: str
):
"""Apply a planned AGENTS.md action to a file.

For "managed", the marker-wrapped section replaces the existing valid
begin..end span; if the file has no valid pair (markers missing, unpaired,
or out of order), stray markers are dropped and the section is prepended,
preserving user content. For "bridge", the one-line import is written.

Args:
file: The file to write.
action: The action to apply.
managed_agents_md_text: The marker-wrapped canonical content.
"""
begin, end = constants.AgentsMd.BEGIN_MARKER, constants.AgentsMd.END_MARKER
if action == "bridge":
content = f"{constants.AgentsMd.CLAUDE_IMPORT}\n"
elif not file.exists():
content = managed_agents_md_text + "\n"
else:
existing = file.read_text()
begin_idx = existing.find(begin)
end_idx = existing.find(end, begin_idx + len(begin)) if begin_idx != -1 else -1
if end_idx != -1:
content = (
existing[:begin_idx]
+ managed_agents_md_text
+ existing[end_idx + len(end) :]
)
else:
# No valid begin..end pair: drop stray markers and prepend the section.
remainder = existing.replace(begin, "").replace(end, "").strip("\n")
content = managed_agents_md_text + (
f"\n\n{remainder}\n" if remainder else "\n"
)
console.debug(f"Creating {file}")
file.write_text(content)


def initialize_agents_md(
agents_file: Path = constants.AgentsMd.FILE,
claude_file: Path = constants.AgentsMd.CLAUDE_FILE,
url: str = constants.AgentsMd.CANONICAL_URL,
):
"""Write or refresh the Reflex-managed section of AGENTS.md and CLAUDE.md.

Fetches the canonical content, then applies the plan from
_plan_agents_md() to each file. A failed fetch is a warning, not an
error, so init still succeeds offline.

Args:
agents_file: The AGENTS.md file to create or refresh in the app root.
claude_file: The CLAUDE.md file bridging AGENTS.md for Claude Code.
url: The canonical AGENTS.md to download.
"""
plan = _plan_agents_md(agents_file, claude_file)

import httpx

console.debug(f"Fetching {url}")
try:
response = net.get(url, timeout=5)
response.raise_for_status()
except httpx.HTTPError as e:
console.warn(f"Failed to fetch AGENTS.md from {url} due to {e}. Skipping.")
return

managed_agents_md_text = (
f"{constants.AgentsMd.BEGIN_MARKER}\n"
f"{response.text.strip()}\n"
f"{constants.AgentsMd.END_MARKER}"
)
for file, action in plan:
_apply_agents_md_action(file, action, managed_agents_md_text)


def _read_dependency_file(file_path: Path) -> tuple[str | None, str | None]:
"""Read a dependency file with a forgiving encoding strategy.

Expand Down
Loading
Loading