Skip to content
Open
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
10 changes: 9 additions & 1 deletion 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`:
The fastest way is to scaffold it as part of a new app. Pass `--agents` to `reflex init` and Reflex writes a starter `AGENTS.md` into the project root alongside `rxconfig.py`:

```bash
reflex init --agents
```

An existing `AGENTS.md` is never overwritten, so it is safe to re-run.

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
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-builder/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 — a starter is below.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you test the efficacy of this line

```

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
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
11 changes: 11 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,17 @@ class GitIgnore(SimpleNamespace):
}


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

# The AGENTS.md file written to the app root.
FILE = Path("AGENTS.md")
# The canonical AGENTS.md maintained in the reflex-dev/agent-skills repo.
CANONICAL_URL = (
"https://raw.githubusercontent.com/reflex-dev/agent-skills/main/AGENTS.md"
)


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()

# Optionally write a sample 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",
is_flag=True,
help="Write a sample AGENTS.md to guide AI coding agents working in the app.",
)
Comment on lines +130 to +134

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought we wanted this be turned on by default?

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
33 changes: 32 additions & 1 deletion reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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 +43,37 @@ def initialize_gitignore(
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")


def initialize_agents_md(
agents_file: Path = constants.AgentsMd.FILE,
url: str = constants.AgentsMd.CANONICAL_URL,
):
"""Write the AGENTS.md for AI coding agents, fetched from the canonical repo.

Does not overwrite an existing file so a user's customizations are preserved.
Aborts if the canonical file cannot be fetched.

Args:
agents_file: The AGENTS.md file to create in the app root.
url: The canonical AGENTS.md to download.
"""
if agents_file.exists():
console.debug(f"{agents_file} already exists, skipping.")
return

import httpx

console.debug(f"Fetching {url}")
try:
response = net.get(url, timeout=5)
response.raise_for_status()
except httpx.HTTPError as e:
console.error(f"Failed to fetch AGENTS.md from {url} due to {e}.")
raise SystemExit(1) from None
Comment on lines +69 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 httpx.HTTPError only covers HTTP-protocol-level errors (like HTTPStatusError for non-2xx responses). Network-level errors — httpx.ConnectError, httpx.TimeoutException, httpx.ReadTimeout, etc. — are subclasses of httpx.RequestError, which is not a subclass of httpx.HTTPError. If the GitHub raw URL is unreachable (DNS failure, timeout, firewall), the exception escapes unhandled and the user sees a raw traceback instead of the clean error message. The companion test test_initialize_agents_md_aborts_on_fetch_failure patches net.get to raise httpx.ConnectError and asserts SystemExit, but with the current catch clause that test will also fail at runtime because ConnectError is not caught.

Suggested change
except httpx.HTTPError as e:
console.error(f"Failed to fetch AGENTS.md from {url} due to {e}.")
raise SystemExit(1) from None
except (httpx.HTTPError, httpx.RequestError) as e:
console.error(f"Failed to fetch AGENTS.md from {url} due to {e}.")
raise SystemExit(1) from None


console.debug(f"Creating {agents_file}")
agents_file.write_text(response.text)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have a starter marker and finish marker so one can add other things in AGENTS.md



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

Expand Down
40 changes: 40 additions & 0 deletions tests/units/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,46 @@ def test_initialize_non_existent_gitignore(
assert set(file_content) - expected == set()


def test_initialize_agents_md_fetches_canonical(tmp_path, mocker):
"""Test that AGENTS.md is fetched from the canonical repo when absent."""
agents_file = tmp_path / "AGENTS.md"
response = mocker.Mock()
response.text = "# canonical agents"
get = mocker.patch("reflex.utils.net.get", return_value=response)

frontend_skeleton.initialize_agents_md(
agents_file=agents_file, url="http://x/AGENTS.md"
)

get.assert_called_once_with("http://x/AGENTS.md", timeout=5)
assert agents_file.read_text() == "# canonical agents"


def test_initialize_agents_md_preserves_existing(tmp_path, mocker):
"""Test that an existing AGENTS.md is never overwritten or re-fetched."""
agents_file = tmp_path / "AGENTS.md"
agents_file.write_text("custom content")
get = mocker.patch("reflex.utils.net.get")

frontend_skeleton.initialize_agents_md(agents_file=agents_file)

assert agents_file.read_text() == "custom content"
get.assert_not_called()


def test_initialize_agents_md_aborts_on_fetch_failure(tmp_path, mocker):
"""Test that a failed fetch aborts init without leaving a partial file."""
import httpx

agents_file = tmp_path / "AGENTS.md"
mocker.patch("reflex.utils.net.get", side_effect=httpx.ConnectError("boom"))

with pytest.raises(SystemExit):
frontend_skeleton.initialize_agents_md(agents_file=agents_file)

assert not agents_file.exists()


def test_initialize_requirements_txt_skips_when_pyproject_exists(tmp_path):
"""Test that pyproject-based apps do not get a requirements.txt file."""
pyproject_file = tmp_path / "pyproject.toml"
Expand Down
Loading