Skip to content
This repository was archived by the owner on May 7, 2026. It is now read-only.
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
75 changes: 69 additions & 6 deletions wren/src/wren/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,56 @@
_TARGET_DIR = "target"
_TARGET_FILE = "mdl.json"

_AGENTS_MD_TEMPLATE = """\
# AGENTS.md

This project uses [Wren Engine](https://github.com/Canner/wren-engine) as the semantic layer for data querying. Queries are written against MDL model names, not raw database tables.

## Answering data questions

When the user asks about data, metrics, reports, or business questions, follow this workflow:

1. `wren memory fetch -q "<question>"` — get relevant schema context
2. `wren memory recall -q "<question>" --limit 3` — find similar past queries
3. Write SQL using model names from the MDL (not raw table names)
4. `wren --sql "<sql>"` — execute through the semantic layer
5. `wren memory store --nl "<question>" --sql "<sql>"` — store confirmed results

If this is the first query in the session, also run `wren context instructions` to load business rules.

## Modifying the data model

When the user wants to add models, change schema, or onboard a new table:

1. Edit YAML files in `models/`, `views/`, or `relationships.yml`
2. `wren context validate` — check structure
3. `wren context build` — compile to `target/mdl.json`
4. `wren memory index` — re-index schema for search

## Prerequisites

This project requires the `wren` CLI. Install with your data source extra:

```bash
pip install "wren-engine[postgres,memory,ui]"
```

Replace `postgres` with your data source (`mysql`, `bigquery`, `snowflake`, `clickhouse`, `trino`, `mssql`, `databricks`, `redshift`, `spark`, `athena`, `oracle`). The `memory` extra enables semantic search; `ui` enables the interactive UI.

See https://docs.getwren.ai/oss/engine/get_started/installation for full setup.

## Quick reference

| Task | Command |
|------|---------|
| Run a query | `wren --sql "SELECT ..."` |
| Preview planned SQL | `wren dry-plan --sql "SELECT ..."` |
| Show available models | `wren context show` |
| Check connection | `wren profile debug` |
| Check memory index | `wren memory status` |
| Rebuild after changes | `wren context build && wren memory index` |
"""


# ── Case conversion ───────────────────────────────────────────────────────

Expand Down Expand Up @@ -195,6 +245,14 @@ def convert_mdl_to_project(mdl_json: dict) -> list[ProjectFile]:
)
)

# ── AGENTS.md ──────────────────────────────────────────────
files.append(
ProjectFile(
relative_path="AGENTS.md",
content=_AGENTS_MD_TEMPLATE,
)
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return files


Expand All @@ -209,10 +267,9 @@ def write_project_files(
Args:
files: List of ProjectFile from convert_mdl_to_project().
output_dir: Target directory.
force: If False, raise SystemExit if wren_project.yml already exists.
force: If False, raise SystemExit if any target file already exists.
"""
output_dir = Path(output_dir)
project_file = output_dir / "wren_project.yml"

if force and output_dir.exists():
import shutil # noqa: PLC0415
Expand All @@ -223,17 +280,23 @@ def write_project_files(
"relationships.yml",
"instructions.md",
"wren_project.yml",
"AGENTS.md",
):
target = output_dir / managed
if target.is_dir():
shutil.rmtree(target)
elif target.exists():
target.unlink()

if project_file.exists() and not force:
raise SystemExit(
f"Error: {project_file} already exists. Use --force to overwrite."
)
if not force:
conflicts = [
f.relative_path for f in files if (output_dir / f.relative_path).exists()
]
if conflicts:
names = ", ".join(f"'{Path(p).name}'" for p in conflicts)
raise SystemExit(
f"Error: {names} already exists. Use --force to overwrite."
)

for f in files:
root = output_dir.resolve()
Expand Down
13 changes: 11 additions & 2 deletions wren/src/wren/context_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ def init(

# ── Scaffold empty project (existing behavior) ────────────
project_file = project_path / "wren_project.yml"
if project_file.exists() and not force:
agents_file = project_path / "AGENTS.md"
conflicts = [f for f in (project_file, agents_file) if f.exists()]
if conflicts and not force:
names = ", ".join(f"'{c.name}'" for c in conflicts)
typer.echo(
f"Error: '{project_file}' already exists. This is already a Wren project.",
f"Error: {names} already exists. Use --force to overwrite.",
err=True,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
raise typer.Exit(1)
Expand Down Expand Up @@ -167,12 +170,18 @@ def init(
"Add custom rules or guidelines for LLM-based query generation here.\n"
)

# ── AGENTS.md ──
from wren.context import _AGENTS_MD_TEMPLATE # noqa: PLC0415

(project_path / "AGENTS.md").write_text(_AGENTS_MD_TEMPLATE)

typer.echo(f"Wren project initialized: {project_path}")
typer.echo(" wren_project.yml — project metadata (edit data_source)")
typer.echo(" models/example/ — example model (metadata.yml)")
typer.echo(" views/example_view/ — example view (metadata.yml + sql.yml)")
typer.echo(" relationships.yml — define joins between models")
typer.echo(" instructions.md — LLM instructions")
typer.echo(" AGENTS.md — AI agent workflow guidance")
typer.echo("\nNext: edit your models, then run `wren context build`.")


Expand Down
9 changes: 7 additions & 2 deletions wren/tests/unit/test_convert_mdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import yaml

from wren.context import (
_AGENTS_MD_TEMPLATE,
_CAMEL_TO_SNAKE_MAP,
_camel_to_snake,
_snake_to_camel,
Expand Down Expand Up @@ -150,6 +151,8 @@ def test_convert_mdl_to_project():
assert "views/monthly_revenue/sql.yml" in file_map
assert "relationships.yml" in file_map
assert "instructions.md" in file_map
assert "AGENTS.md" in file_map
assert file_map["AGENTS.md"] == _AGENTS_MD_TEMPLATE

# wren_project.yml
project = yaml.safe_load(file_map["wren_project.yml"])
Expand Down Expand Up @@ -206,6 +209,8 @@ def test_write_project_files(tmp_path: Path):
assert (tmp_path / "views" / "monthly_revenue" / "sql.yml").exists()
assert (tmp_path / "relationships.yml").exists()
assert (tmp_path / "instructions.md").exists()
assert (tmp_path / "AGENTS.md").exists()
assert (tmp_path / "AGENTS.md").read_text() == _AGENTS_MD_TEMPLATE


def test_write_project_files_refuses_overwrite(tmp_path: Path):
Expand Down Expand Up @@ -251,11 +256,11 @@ def test_convert_then_build_roundtrip(tmp_path: Path):


def test_empty_mdl():
"""Empty models/views/relationships — only wren_project.yml is produced."""
"""Empty models/views/relationships — only wren_project.yml and AGENTS.md are produced."""
mdl = {"catalog": "wren", "schema": "public"}
files = convert_mdl_to_project(mdl)
paths = {f.relative_path for f in files}
assert paths == {"wren_project.yml"}
assert paths == {"wren_project.yml", "AGENTS.md"}
assert "instructions.md" not in paths


Expand Down
Loading