Skip to content

Get started with a coding agent

You can use a coding agent to quickly get up and running with RelationalAI (RAI). This tutorial introduces a repeatable workflow using RAI’s agent skills. Whether you’re a data analyst or an application developer, you can use this workflow to turn raw Snowflake data into a semantic model that you can reason over, question, and improve.

You’ll learn how to:

  • Set up a coding agent and install the RAI skills.
  • Build a starter semantic model from data in Snowflake.
  • Explore the model with natural-language queries.
  • Push the model toward real decision support by leveraging RAI’s advanced reasoning capabilities.
  • You have access to a Snowflake account with the RelationalAI (RAI) Native App installed.

    How do I know if the RAI Native App is installed? To check if the RAI Native App is installed in your Snowflake account:

    1. Log in to your Snowflake account.
    2. Run the following SQL command in a worksheet:
      SHOW APPLICATIONS;
    3. Check the output for an application named RELATIONALAI.
      • If you see it listed, the RAI Native App is installed.
      • If not, you need to install it.

    How do I install the RAI Native App? See Install the RAI Native App for Snowflake for instructions.

  • You have a Snowflake user with the RAI_DEVELOPER database role.

    How do I check my roles? To check your roles in Snowflake:

    1. Log in to your Snowflake account.
    2. Run the following SQL command in a worksheet:
      SHOW GRANTS TO USER <your_username>;
    3. Check the output in the name column for the RAI_DEVELOPER role.
      • If you see it listed, you have the required role.
      • If not, contact your Snowflake account administrator to have the role assigned to you. See Set Up User Access for the RAI Native App for more information.
  • You can create tables in a Snowflake database and schema for the tutorial data.

    How do I create a sample database and schema for this tutorial? Any Snowflake database and schema will work for this tutorial, as long as you have permission to create tables in that schema. You can use an existing database and schema, or create a new one specifically for this tutorial.

    If you do not already have a writable database and schema for this tutorial, ask a Snowflake administrator to run the following SQL as ACCOUNTADMIN or another role with permission to create databases, create schemas, and grant privileges:

    CREATE DATABASE IF NOT EXISTS RAI_TUTORIAL;
    CREATE SCHEMA IF NOT EXISTS RAI_TUTORIAL.ENERGY_GRID;
    GRANT USAGE ON DATABASE RAI_TUTORIAL TO ROLE rai_developer;
    GRANT USAGE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;
    GRANT CREATE TABLE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;

    After that, use RAI_TUTORIAL.ENERGY_GRID when the tutorial asks for your Snowflake schema.

Choose the tab for your coding agent to see the installation instructions. For more details and other installation options, see Install RAI agent skills.

In Claude Code, add the RelationalAI marketplace and install the rai plugin:

/plugin marketplace add RelationalAI/rai-agent-skills
/plugin install rai@RelationalAI

To get started, download the RelationalAI starter project. This project contains a simple PyRel project structure:

  • Directorydata/ Sample CSV files
  • Directorymodel Starter PyRel semantic model package
    • __init__.py
    • schema.py Defines the model’s schema
    • sources.py Maps source data to the schema
  • load_data.py Creates Snowflake tables from sample data
  • pyproject.toml Define project metadata and dependencies
  • queries.py Contains example queries
  • raiconfig.yaml Stores the RAI settings for this project

Don’t worry about the contents of the starter project just yet. You’ll explore them later in this tutorial as you build a PyRel semantic model for the sample data.

Follow these steps to download the starter project and set up your project environment.

  1. Download and extract the ZIP file.

    Copy and paste the following prompt into your coding agent to download the starter project and place it in a new project directory:

    Create a new directory called rai-agent-skills-tutorial.
    Download the starter project from https://docs.relational.ai/rai-starter-project.zip into that directory, extract it there, and then remove the downloaded zip file.
  2. Set up the project environment.

    Use uv to create the project environment and install the dependencies pinned in pyproject.toml.

    Copy and paste the following prompt into your coding agent to set up the starter project environment:

    Check whether uv is installed in this project. If it is missing, install it first. Then run uv sync in the current project directory to create the virtual environment and install the dependencies from pyproject.toml.

The starter project assumes you use a programmatic access token (PAT) to authenticate with Snowflake. We recommend using a PAT for most use cases, but you can use any Snowflake authentication method.

Follow the steps below to add your Snowflake PAT credentials, or use the /rai-setup skill to help you set up a different authentication method.

  1. Gather your Snowflake connection details.

    You will need:

    DetailWhere to find it
    AccountLog in to the Snowflake account that has the RAI Native App installed. Execute the following SQL command in a worksheet to find your account name:
    SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME() AS account_identifier;
    UserThe Snowflake username you will use to connect. This user must have the RAI_DEVELOPER role.
    TokenThe programmatic access token (PAT) you use to authenticate with Snowflake. If you don’t have one, ask a Snowflake admin to follow the steps in Configure programmatic access token authentication to create one.
    WarehouseThe name of the Snowflake warehouse you want to use to execute SQL queries. Can be any existing X-SMALL or larger warehouse.
  2. Edit the raiconfig.yaml file.

    Open raiconfig.yaml and replace the placeholders for account, warehouse, user, and token with your Snowflake connection details and PAT credentials. The role field is already set to RAI_DEVELOPER, so you can leave that as is unless you know you need to use a different role.

  3. Validate the connection.

    After you have added your Snowflake credentials to raiconfig.yaml, run the following command in your terminal to validate the connection:

    Terminal window
    uv run rai connect

    If the connection is successful, you will see a message indicating that the connection to Snowflake was established successfully.

The starter project includes a load_data.py script that creates tutorial tables in the Snowflake namespace you specify and loads the sample CSV files into them.

What is in the sample data? The sample data represents a dataset representing the Texas electric grid’s substations, generators, load zones, and transmission lines. It also includes data for requests to interconnect new data centers to the grid, as well as current load readings, demand forecasts, maintenance windows, and grid upgrades.

Choose a tab below to see the instructions for loading the sample data into Snowflake.

Copy and paste the following prompt into your coding agent to load the sample data into Snowflake and clean up the local starter files afterward:

Ask me for the Snowflake database and schema name in `DATABASE.SCHEMA` form. Then run `uv run load_data.py <DATABASE.SCHEMA>` in the current project directory to load the sample data into Snowflake. After the data is loaded successfully, remove the local `data/` directory and `load_data.py`.

Start building with /rai-build-starter-ontology

Section titled “Start building with /rai-build-starter-ontology”

Follow these steps to have your agent build a starter PyRel semantic model based on the sample data you just loaded into Snowflake.

  1. Invoke the /rai-build-starter-ontology skill.

    Use the following prompt to have your agent build a PyRel semantic model based on your Snowflake data:

    /rai-build-starter-ontology Electric grid and datacenter capacity planning, with data from my Snowflake account. Model substations, load zones, generators, transmission lines, demand, forecasts, upgrades, maintenance, customers, and datacenter requests.
    Do the following:
    - Edit the existing model/ directory structure in this project with the new semantic model.
    - Update the `queries.py` file with one query that counts the number of data center requests in the model.
    Before you start, ask me for the Snowflake schema (in the format `DATABASE.SCHEMA`) to point at for the source data.
    • Inspects the Snowflake tables to understand the sample data.
    • Defines concepts, properties, and relationships in model/schema.py with a PyRel Model object.
    • Maps Snowflake tables to the model in model/sources.py with PyRel Table objects and definitions.
    • Adds a query to queries.py that counts data center requests.
  2. Enter your Snowflake schema.

    When prompted, enter the Snowflake schema you used when loading the sample data in the previous step.

  3. Choose one-shot mode.

    When prompted by your agent, choose one-shot mode for this tutorial so that the agent can complete the model-building process in a single pass.

  4. Review the generated model.

    After the agent finishes building the model, review the generated files in the model/ directory and the updated queries.py file to ensure they meet your expectations. Here are some samples of outputs you can expect to see in the generated files:

    Click to expand the sample output from /rai-build-starter-ontology
    """Semantic model for electric-grid and data-center capacity planning."""
    from relationalai.semantics import Date, Float, Integer, Model, String
    model = Model("EnergyGridCapacityPlanning")
    LoadZone = model.Concept("LoadZone", identify_by={"id": String})
    LoadZone.name = model.Property(f"{LoadZone} has {String:name}")
    LoadZone.peak_demand_mw = model.Property(f"{LoadZone} has {Float:peak_demand_mw}")
    LoadZone.base_demand_mw = model.Property(f"{LoadZone} has {Float:base_demand_mw}")
    Customer = model.Concept("Customer", identify_by={"id": String})
    Customer.name = model.Property(f"{Customer} has {String:name}")
    Customer.load_zone = model.Property(f"{Customer} is served by {LoadZone:load_zone}")
    Customer.contracted_demand_mw = model.Property(
    f"{Customer} has {Float:contracted_demand_mw}"
    )
    Customer.flexibility_pct = model.Property(f"{Customer} has {Float:flexibility_pct}")
    Customer.curtailment_cost_per_mwh = model.Property(
    f"{Customer} has {Float:curtailment_cost_per_mwh}"
    )
    Substation = model.Concept("Substation", identify_by={"id": String})
    Substation.name = model.Property(f"{Substation} has {String:name}")
    Substation.voltage_kv = model.Property(f"{Substation} has {Integer:voltage_kv}")
    Substation.max_capacity_mw = model.Property(f"{Substation} has {Float:max_capacity_mw}")
    Substation.current_load_mw = model.Property(f"{Substation} has {Float:current_load_mw}")
    DataCenterRequest = model.Concept("DataCenterRequest", identify_by={"id": String})
    DataCenterRequest.name = model.Property(f"{DataCenterRequest} has {String:name}")
    DataCenterRequest.hyperscaler = model.Property(
    f"{DataCenterRequest} has {String:hyperscaler}"
    )
    DataCenterRequest.substation = model.Property(
    f"{DataCenterRequest} interconnects at {Substation:substation}"
    )
    DataCenterRequest.requested_mw = model.Property(f"{DataCenterRequest} has {Float:requested_mw}")
    DataCenterRequest.annual_revenue_per_mw = model.Property(
    f"{DataCenterRequest} has {Float:annual_revenue_per_mw}"
    )
    DataCenterRequest.pue = model.Property(f"{DataCenterRequest} has {Float:pue}")
    DataCenterRequest.low_carbon_requirement_pct = model.Property(
    f"{DataCenterRequest} has {Float:low_carbon_requirement_pct}"
    )
    DataCenterRequest.queue_position = model.Property(
    f"{DataCenterRequest} has {Integer:queue_position}"
    )
    DataCenterRequest.status = model.Property(f"{DataCenterRequest} has {String:status}")
    DataCenterRequest.is_ai_workload = model.Relationship(f"{DataCenterRequest} is AI workload")
    DemandPeriod = model.Concept("DemandPeriod", identify_by={"id": String})
    DemandPeriod.load_zone = model.Property(f"{DemandPeriod} is for {LoadZone:load_zone}")
    DemandPeriod.period = model.Property(f"{DemandPeriod} has {Integer:period}")
    DemandPeriod.demand_mw = model.Property(f"{DemandPeriod} has {Float:demand_mw}")
    DemandPeriod.price_per_mwh = model.Property(f"{DemandPeriod} has {Float:price_per_mwh}")
    DemandForecast = model.Concept("DemandForecast", identify_by={"id": String})
    DemandForecast.substation = model.Property(f"{DemandForecast} is for {Substation:substation}")
    DemandForecast.forecast_period = model.Property(
    f"{DemandForecast} has {Integer:forecast_period}"
    )
    DemandForecast.predicted_load_mw = model.Property(
    f"{DemandForecast} has {Float:predicted_load_mw}"
    )
    DemandForecast.confidence = model.Property(f"{DemandForecast} has {Float:confidence}")
    DemandForecast.includes_dc_growth = model.Relationship(
    f"{DemandForecast} includes data center growth"
    )
    Generator = model.Concept("Generator", identify_by={"id": String})
    Generator.name = model.Property(f"{Generator} has {String:name}")
    Generator.gen_type = model.Property(f"{Generator} has {String:gen_type}")
    Generator.substation = model.Property(f"{Generator} connects at {Substation:substation}")
    Generator.capacity_mw = model.Property(f"{Generator} has {Float:capacity_mw}")
    Generator.marginal_cost = model.Property(f"{Generator} has {Float:marginal_cost}")
    Generator.is_renewable = model.Relationship(f"{Generator} is renewable")
    LoadReading = model.Concept("LoadReading", identify_by={"reading_id": Integer})
    LoadReading.substation = model.Property(f"{LoadReading} measures {Substation:substation}")
    LoadReading.reading_date = model.Property(f"{LoadReading} has {Date:reading_date}")
    LoadReading.load_mw = model.Property(f"{LoadReading} has {Float:load_mw}")
    LoadReading.temperature_f = model.Property(f"{LoadReading} has {Float:temperature_f}")
    LoadReading.is_peak_season = model.Relationship(f"{LoadReading} is in peak season")
    RenewableProfile = model.Concept("RenewableProfile", identify_by={"id": String})
    RenewableProfile.generator = model.Property(
    f"{RenewableProfile} is for {Generator:generator}"
    )
    RenewableProfile.period = model.Property(f"{RenewableProfile} has {Integer:period}")
    RenewableProfile.capacity_factor = model.Property(
    f"{RenewableProfile} has {Float:capacity_factor}"
    )
    SubstationUpgrade = model.Concept("SubstationUpgrade", identify_by={"id": String})
    SubstationUpgrade.substation = model.Property(
    f"{SubstationUpgrade} expands {Substation:substation}"
    )
    SubstationUpgrade.capacity_increase_mw = model.Property(
    f"{SubstationUpgrade} has {Float:capacity_increase_mw}"
    )
    SubstationUpgrade.cost_million = model.Property(
    f"{SubstationUpgrade} has {Float:cost_million}"
    )
    SubstationUpgrade.lead_time_months = model.Property(
    f"{SubstationUpgrade} has {Integer:lead_time_months}"
    )
    SubstationUpgrade.enables_low_carbon = model.Relationship(
    f"{SubstationUpgrade} enables low carbon service"
    )
    TransmissionLine = model.Concept("TransmissionLine", identify_by={"id": String})
    TransmissionLine.from_substation = model.Property(
    f"{TransmissionLine} originates at {Substation:from_substation}"
    )
    TransmissionLine.to_substation = model.Property(
    f"{TransmissionLine} terminates at {Substation:to_substation}"
    )
    TransmissionLine.capacity_mw = model.Property(f"{TransmissionLine} has {Float:capacity_mw}")
    TransmissionLine.maintenance_priority = model.Property(
    f"{TransmissionLine} has {String:maintenance_priority}"
    )
    TransmissionLine.is_active = model.Relationship(f"{TransmissionLine} is active")
    MaintenanceWindow = model.Concept("MaintenanceWindow", identify_by={"id": String})
    MaintenanceWindow.asset_type = model.Property(f"{MaintenanceWindow} has {String:asset_type}")
    MaintenanceWindow.start_period = model.Property(
    f"{MaintenanceWindow} has {Integer:start_period}"
    )
    MaintenanceWindow.end_period = model.Property(f"{MaintenanceWindow} has {Integer:end_period}")
    MaintenanceWindow.generator = model.Property(
    f"{MaintenanceWindow} affects {Generator:generator}"
    )
    MaintenanceWindow.transmission_line = model.Property(
    f"{MaintenanceWindow} affects {TransmissionLine:transmission_line}"
    )
    MaintenanceWindow.is_planned = model.Relationship(f"{MaintenanceWindow} is planned")

Use the /rai-querying skill to have your agent answer basic questions about the semantic model. Anything you could ask in a SQL query can be expressed in natural language, and your agent will translate it into a PyRel query and execute it against the model.

  1. Invoke the /rai-querying skill.

    Copy and paste the following prompt into your coding agent to have it answer a basic question about the semantic model:

    /rai-querying What substation is currently most constrained in terms of load capacity? What percentage of its maximum capacity is currently being used? What customers would be impacted if the substation went offline?
  2. Review the agent’s response.

    The agent will generate a PyRel query to answer your question and execute it against the semantic model. It may or may not edit queries.py as part of the process.

    Click to expand the sample response from /rai-querying

    The most constrained substation in the current model is queries.py: SUB-001, with 1250.0 MW of current load against 1800.0 MW max capacity. That puts it at 69.44% utilization. I added the focused report in queries.py and validated it by running python queries.py.

    The customer-impact part is not answerable from the current ontology. Customers are linked to load zones, but there is no modeled path from load zones to substations, so the report now returns that limitation explicitly in queries.py.

    1. If you want the impacted-customer query next, add a source-backed load-zone-to-substation mapping and I can wire it into the ontology.
    2. If that mapping already exists in Snowflake but is just not modeled yet, point me to the table or columns and I’ll implement the full query.

Use /rai-discovery to unlock advanced reasoning capabilities

Section titled “Use /rai-discovery to unlock advanced reasoning capabilities”

RelationalAI’s advanced reasoning capabilities allow you to model complex ideas and find answers to challenging business questions.

Follow these steps to have your agent explain how to use advanced reasoning to answer a complex business question.

  1. Explore an open-ended question.

    Use the /rai-discovery skill with an open-ended question to have your agent explain how to use advanced reasoning to answer it:

    /rai-discovery I need to determine which datacenter requests to approve and which upgrades to fund at what budget level.

    Your agent will:

    • Investigate how the semantic model can be used to answer this question
    • Determine what parts of the question are feasible and what parts are not
    • Explain how RAI’s reasoning capabilities can be used to answer the question.

    Click to expand the sample response from /rai-discovery Your question is squarely prescriptive — a coupled binary-selection problem (approve DataCenterRequest × fund SubstationUpgrade) with budget as a scenario parameter. The ontology supports it directly: READY.

    Primary suggestion — approve & fund

    Statement. Choose which data center requests to approve and which substation upgrades to fund so that approved load fits within (upgraded) substation capacity, total upgrade spend stays under the budget, and total annual revenue is maximized — repeated across budget levels to see the value curve.

    FieldValue
    Reasonerprescriptive
    FeasibilityREADY
    Decision scopeDataCenterRequest.approved (binary), SubstationUpgrade.funded (binary)
    Forcing requirementEvery approved request’s requested_mw must be served by its target Substation’s available headroom (= max_capacity_mw − current_load_mw + Σ funded upgrades' capacity_increase_mw at that substation)
    Objective propertymaximize Σ DataCenterRequest.requested_mw × annual_revenue_per_mw over approved requests
    Scenario parametertotal upgrade budget (Σ funded SubstationUpgrade.cost_million ≤ B), swept across levels (e.g. 100M / 500M / unbounded)
    Competing objectivesOptional second pass: revenue vs. low-carbon shortfall (sum of low_carbon_requirement_pct × requested_mw shortfall vs. renewable supply at substation, derivable from Generator.is_renewable + Generator.substation)

    Why this maps cleanly. Each DataCenterRequest.substation and each SubstationUpgrade.substation already resolve to the same Substation, so the capacity coupling between the two decisions is a straightforward grouped sum. Budget is a single global constraint over SubstationUpgrade.cost_million. Revenue is a linear function over approved requests.

    Adjacent suggestions worth keeping on the table

    1. Queue-fairness variant (rules + prescriptive). Use DataCenterRequest.queue_position and status to add a fairness rule — e.g. don’t approve a request if an earlier-queued request at the same substation is denied. Sharper realism, slightly tighter feasible set. Status: READY.

    2. Low-carbon-aware variant (prescriptive, multi-objective). Add a constraint or soft penalty: for each approved request, the renewable share of its substation’s supply (renewable generator capacity at that substation ÷ total generator capacity at that substation) must meet low_carbon_requirement_pct. Funded upgrades that enables_low_carbon raise the supply side. Reveals the revenue ↔ low-carbon trade-off. Status: READY.

    3. Forecast-driven upgrade selection (predictive → prescriptive chain). Use DemandForecast.predicted_load_mw per substation/period to pick upgrades that relieve substations projected to be saturated regardless of DC approvals — gives a “no-regret” upgrade subset before the joint optimization. Status: READY (forecasts already in model).

    Recommended next step

    Proceed with the primary suggestion. To run it I’ll need to load rai-prescriptive-problem-formulationrai-prescriptive-solver-managementrai-prescriptive-results-interpretation (plus rai-querying / rai-pyrel-coding).

    Before I do — two quick choices:

    1. Budget levels to sweep? (e.g. [50, 100, 200, 500] $M, plus unbounded) — or a single budget you have in mind.
    2. Low-carbon: hard constraint, soft penalty, or skip for v1? Skipping keeps v1 a clean single-objective MILP; adding it turns it into a Pareto / scenario question.
  2. Provide more details to get a more actionable answer.

    Use a more detailed prompt to get a plan that is:

    • More likely to be complete and aligned with your business goals.
    • Auditable and explainable. An expert can review the plan and modify it as needed before you run it, which is important for high-stakes business decisions.

    For example, the following prompt asks the agent to explore solutions that involve chains of reasoning, recommend the best solution, and break it down into actionable steps complete with copy-pastable prompts that invoke the appropriate skills for each step:

    /rai-discovery I need to determine which datacenter requests to approve and which upgrades to fund at what budget level.
    Give me the best option for answering this question that makes use of all three reasoning types in a reasoning chain: rules-based, graph, and prescriptive reasoning.
    Break the solution into three copy-pastable prompts that make use of the appropriate skills for each step in the reasoning chain:
    - Use the smallest number of skills required for each prompt.
    - Keep each step as simple as possible while preserving real business value.
    - Keep prompts fully self self-contained.
    - Include step-by-step instructions.
    - Explicitly state which files to add/edit. All model additions should be made to files in the `model/` directory. Verification and summary reports should be done in `queries.py` or in a new file in the project root with a descriptive name.
    - Instruct the agent to summarize what it did and explain results in plain English.
    Use the following template for your output. Write explanation, benefits, and tradeoffs in clear, concise, plain English.
    --- TEMPLATE ---
    <Describe the option in a few sentences. Explain how each step reinforces the next.>
    **Benefits:**
    - Benefit 1
    - Benefit 2
    - ...
    **Trade-offs:**
    - Trade-off 1
    - Trade-off 2
    - ...
    **Step 1:**
    ```text
    /skill1 [+ /skill2 ...] <PROMPT>
    ```
    **Step 2:**
    ```text
    [/skill1 [+ /skill2 ...]] <PROMPT>
    ```
    **Step 3:**
    ```text
    [/skill1 [+ /skill2 ...]] <PROMPT>
    ```
    • Asks the agent to combine rules, graph, and prescriptive reasoning in a single chain instead of picking just one.
    • Produces one prompt per step so you can work through the solution incrementally.
    • Keeps prompts self-contained and detailed enough to be copy-pasted into your agent without additional context.
    • Generates detailed, step-by-step prompts that are auditable and explainable, and help keep agents on track.

    Click to expand the sample response from /rai-discovery This chain answers your question by first filtering for what’s eligible, then identifying what matters most structurally in the grid, then solving which to approve and fund under a budget. Rules trim the candidate set and pre-compute available headroom so the optimizer has clean inputs. Graph centrality assigns each substation a structural-importance score so upgrades at network hubs (which unlock capacity for many downstream nodes) are favored. The prescriptive solver picks the binary approve/fund decisions that maximize centrality-weighted revenue under a budget, and re-runs at several budget levels so you can see the marginal return on each extra dollar.

    Benefits:

    • Cleanly separates eligibility (rules), network value (graph), and decision-making (optimization) — each stage is independently inspectable.
    • Centrality weighting captures the intuition that an upgrade at a hub substation has more value than one at a leaf, even when MW and cost look identical.
    • The budget-sweep scenario directly answers “at what budget level?” by showing approvals and revenue at each budget point.
    • All three stages enrich the same ontology, so downstream queries can introspect intermediate results.

    Trade-offs:

    • Centrality is a proxy for network value, not a load-flow simulation — true grid impact would require power-flow modeling.
    • The optimizer assumes a single planning horizon (no upgrade lead-time scheduling); lead_time_months is reported but not enforced.
    • Eligibility rules are heuristic thresholds; tightening or loosening them shifts the candidate pool and can change the answer.

    Step 1:

    /rai-rules-authoring
    Project context:
    - RAI tutorial project at the workspace root with model files in `model/`.
    - `model/schema.py` already defines: Substation, LoadZone, Generator, TransmissionLine, DemandPeriod, DemandForecast, SubstationUpgrade, MaintenanceWindow, Customer, DataCenterRequest.
    - `model/sources.py` binds them to Snowflake tables in TUTORIALS.ENERGY_GRID.
    - `model/__init__.py` already does `from .rules import *`, so create a `model/rules.py` file.
    Task: Author derived-property rules that prepare clean inputs for a later optimization stage. Add the following to a new file `model/rules.py`:
    1. On Substation, derive:
    - `available_headroom_mw` = max_capacity_mw - current_load_mw (clamped at 0 minimum).
    - `is_constrained` flag: True when current_load_mw / max_capacity_mw >= 0.85.
    2. On DataCenterRequest, derive:
    - `is_eligible` flag: True when status == "pending" AND requested_mw > 0 AND annual_revenue_per_mw > 0. Reject everything else from consideration.
    - `expected_annual_revenue` = requested_mw * annual_revenue_per_mw.
    - `requires_low_carbon` flag: True when low_carbon_requirement_pct >= 50.0.
    3. On SubstationUpgrade, derive:
    - `mw_per_million` = capacity_increase_mw / cost_million (efficiency score; guard against zero cost).
    - `is_high_value` flag: True when mw_per_million is in the top quartile across all upgrades OR enables_low_carbon is True.
    Then, in `queries.py`, add and run a `rules_summary()` function that prints:
    - Count of eligible DataCenterRequests and their total requested_mw and total expected_annual_revenue.
    - Count of constrained substations and their average utilization.
    - Count of high-value upgrades and their total cost_million.
    - A small sample (top 5 by expected_annual_revenue) of eligible requests with their substation_id and available_headroom_mw at that substation.
    After running, summarize in plain English: how many requests passed eligibility, which substations are most squeezed, and which upgrades look most cost-efficient. Do not modify schema.py or sources.py.

    Step 2:

    /rai-graph-analysis
    Project context:
    - RAI tutorial project at workspace root. `model/schema.py` defines Substation and TransmissionLine; TransmissionLine has from_substation, to_substation, capacity_mw, and an is_active flag.
    - Rules from Step 1 have been added in `model/rules.py` (Substation.available_headroom_mw / is_constrained, DataCenterRequest eligibility flags, SubstationUpgrade.is_high_value).
    Task: Build the grid transmission graph and compute per-substation structural importance, then write the score back onto the ontology so Step 3 can use it.
    1. Create a new file `model/graph.py` that:
    - Constructs an undirected, weighted graph: nodes = Substation, edges = active TransmissionLine pairs (use `is_active`), edge weight = capacity_mw.
    - Runs eigenvector centrality on the graph.
    - Defines a new property `Substation.centrality_score` (Float) and binds the centrality result to it via `model.define()` so it is queryable like any other property.
    - Also defines `Substation.network_weight` = 1.0 + centrality_score (so leaf nodes still get a non-zero multiplier in optimization).
    2. In `queries.py`, add and run a `centrality_report()` function that prints the top 10 substations by centrality_score along with their available_headroom_mw and whether they are targeted by any pending DataCenterRequest.
    After running, summarize in plain English: which substations are the structural hubs of the network, whether the hubs are also the constrained ones, and whether high-revenue datacenter requests are clustering at hubs or at peripheral substations. Do not modify schema.py, sources.py, or rules.py.

    Step 3:

    /rai-prescriptive-problem-formulation + /rai-prescriptive-solver-management + /rai-prescriptive-results-interpretation
    Project context:
    - RAI tutorial project at workspace root. `model/schema.py` + `model/sources.py` define the base ontology.
    - `model/rules.py` provides DataCenterRequest.is_eligible / .expected_annual_revenue, Substation.available_headroom_mw, SubstationUpgrade.is_high_value, etc.
    - `model/graph.py` provides Substation.centrality_score and Substation.network_weight.
    Task: Formulate, solve, and interpret a portfolio decision that picks which eligible DataCenterRequests to approve and which SubstationUpgrades to fund under a budget.
    1. Create `model/optimization.py` with the prescriptive formulation:
    - Binary decision: `DataCenterRequest.approve` on each eligible request (filter by is_eligible).
    - Binary decision: `SubstationUpgrade.fund` on every upgrade.
    - Capacity constraint per Substation: sum(approve * requested_mw of requests targeting that substation) <= available_headroom_mw + sum(fund * capacity_increase_mw of upgrades at that substation).
    - Budget constraint (global): sum(fund * cost_million) <= BUDGET_MILLION (parameter — see below).
    - Objective: maximize sum over approved requests of (expected_annual_revenue * network_weight of target substation). Centrality-weighting biases approvals toward hubs.
    - Expose BUDGET_MILLION as a module-level parameter that can be set before solving.
    2. Create a new project-root file `portfolio_decision.py` that:
    - Solves the problem at three budget levels: $50M, $150M, $300M.
    - For each budget, extracts: list of approved requests (id, name, requested_mw, expected_annual_revenue, target substation, centrality), list of funded upgrades (id, substation, capacity_increase_mw, cost_million), total expected annual revenue, total upgrade spend, number of constrained substations relieved.
    - Prints a side-by-side comparison table across the three budgets.
    3. After running, write a plain-English executive summary covering:
    - Which datacenter requests should be approved at each budget level and why.
    - Which upgrades should be funded at each budget level and why (cost-efficiency vs. network position).
    - The marginal return: how much extra revenue each additional $100M of budget unlocks.
    - The recommended budget level and the reasoning behind it.
    Only add files as specified; do not modify schema.py, sources.py, rules.py, or graph.py.

Encode business rules with /rai-rules-authoring

Section titled “Encode business rules with /rai-rules-authoring”

The /rai-rules-authoring skill enables your agent to add complex rules-based reasoning to your semantic model. Rules-based reasoning uses if-then logic to express business rules, such as “if a substation reaches 85% of its maximum capacity, mark it as constrained.”

Follow these steps to have your agent add rules to your model.

  1. Invoke the /rai-rules-authoring skill.

    Use a detailed prompt that explains what you want to accomplish and any important decision points or constraints.

    For example, the following prompt is copied from the sample output in the previous section and pre-screens datacenter requests to help identify any that are clearly infeasible or require upgrades to be approved:

    /rai-rules-authoring
    Project context:
    - RAI tutorial project at the workspace root with model files in `model/`.
    - `model/schema.py` already defines: Substation, LoadZone, Generator, TransmissionLine, DemandPeriod, DemandForecast, SubstationUpgrade, MaintenanceWindow, Customer, DataCenterRequest.
    - `model/sources.py` binds them to Snowflake tables in TUTORIALS.ENERGY_GRID.
    - `model/__init__.py` already does `from .rules import *`, so create a `model/rules.py` file.
    Task: Author derived-property rules that prepare clean inputs for a later optimization stage. Add the following to a new file `model/rules.py`:
    1. On Substation, derive:
    - `available_headroom_mw` = max_capacity_mw - current_load_mw (clamped at 0 minimum).
    - `is_constrained` flag: True when current_load_mw / max_capacity_mw >= 0.85.
    2. On DataCenterRequest, derive:
    - `is_eligible` flag: True when status == "pending" AND requested_mw > 0 AND annual_revenue_per_mw > 0. Reject everything else from consideration.
    - `expected_annual_revenue` = requested_mw * annual_revenue_per_mw.
    - `requires_low_carbon` flag: True when low_carbon_requirement_pct >= 50.0.
    3. On SubstationUpgrade, derive:
    - `mw_per_million` = capacity_increase_mw / cost_million (efficiency score; guard against zero cost).
    - `is_high_value` flag: True when mw_per_million is in the top quartile across all upgrades OR enables_low_carbon is True.
    Then, in `queries.py`, add and run a `rules_summary()` function that prints:
    - Count of eligible DataCenterRequests and their total requested_mw and total expected_annual_revenue.
    - Count of constrained substations and their average utilization.
    - Count of high-value upgrades and their total cost_million.
    - A small sample (top 5 by expected_annual_revenue) of eligible requests with their substation_id and available_headroom_mw at that substation.
    After running, summarize in plain English: how many requests passed eligibility, which substations are most squeezed, and which upgrades look most cost-efficient. Do not modify schema.py or sources.py.
    • Creates a new model/rules.py file for the derived properties used in later steps.
    • Calculates substation headroom and flags substations that are already constrained.
    • Filters datacenter requests to eligible requests and calculates their expected annual revenue.
    • Scores upgrades by capacity per dollar and flags high-value upgrades.
    • Adds a rules_summary() query that explains the eligible requests, constrained substations, and cost-efficient upgrades in plain English.
  2. Review the agent’s response.

    Your agent will generate PyRel code to implement the rules and execute queries against the model. You should see:

    • A new model/rules.py file that implements the rules described in the prompt.
    • An updated queries.py file that includes the rules_summary() function.
    • An in chat summary describing what was implemented and the results of running the rules_summary() query.
    Click to expand the sample response from /rai-rules-authoring

    Implemented the derived rules in model/rules.py and added rules_summary() in queries.py. I left schema.py and sources.py untouched.

    Ran source .venv/bin/activate && python queries.py successfully. Results:

    • 10 data center requests passed eligibility, totaling 2930.0 MW and $528,200,000 expected annual revenue.
    • 0 substations crossed the >= 85% constrained threshold. The most squeezed by utilization are SUB-001 Houston Ship Channel at 69.44%, SUB-004 Austin Energy at 68.89%, and SUB-002 Dallas-Fort Worth at 68.75%.
    • 7 upgrades are high-value by top-quartile efficiency or low-carbon enablement, totaling $460.0M.
    • Most cost-efficient upgrades are UPG-008 and UPG-009, tied at 5.0 MW per $M, followed by UPG-001, UPG-005, and UPG-007.

    The top eligible request sample is led by DC-004 xAI Colossus Texas at $105M expected annual revenue, targeting SUB-002 with 500 MW available headroom.

Reveal hidden structure with /rai-graph-analysis

Section titled “Reveal hidden structure with /rai-graph-analysis”

The /rai-graph-analysis skill enables your agent to use graph reasoning to better understand the relationships between entities in your model. For instance, you can use graph reasoning to identify critical nodes in the energy grid network that would have a disproportionate impact on the system if they were to fail.

Follow these steps to have your agent analyze the graph structure of your model.

  1. Invoke the /rai-graph-analysis skill.

    Use a detailed prompt that explains which graph to build, which graph metrics to calculate, and how the results should be saved back to the model.

    For example, the following prompt is copied from the sample output in the /rai-discovery section and scores substations by their structural importance in the transmission network:

    /rai-graph-analysis
    Project context:
    - RAI tutorial project at workspace root. `model/schema.py` defines Substation and TransmissionLine; TransmissionLine has from_substation, to_substation, capacity_mw, and an is_active flag.
    - Rules from Step 1 have been added in `model/rules.py` (Substation.available_headroom_mw / is_constrained, DataCenterRequest eligibility flags, SubstationUpgrade.is_high_value).
    Task: Build the grid transmission graph and compute per-substation structural importance, then write the score back onto the ontology so Step 3 can use it.
    1. Create a new file `model/graph.py` that:
    - Constructs an undirected, weighted graph: nodes = Substation, edges = active TransmissionLine pairs (use `is_active`), edge weight = capacity_mw.
    - Runs eigenvector centrality on the graph.
    - Defines a new property `Substation.centrality_score` (Float) and binds the centrality result to it via `model.define()` so it is queryable like any other property.
    - Also defines `Substation.network_weight` = 1.0 + centrality_score (so leaf nodes still get a non-zero multiplier in optimization).
    2. In `queries.py`, add and run a `centrality_report()` function that prints the top 10 substations by centrality_score along with their available_headroom_mw and whether they are targeted by any pending DataCenterRequest.
    After running, summarize in plain English: which substations are the structural hubs of the network, whether the hubs are also the constrained ones, and whether high-revenue datacenter requests are clustering at hubs or at peripheral substations. Do not modify schema.py, sources.py, or rules.py.
    • Builds a graph from active transmission lines between substations.
    • Scores substations by how important they are to the grid structure.
    • Adds graph results to the model, including Substation.centrality_score, Substation.component_id, and strategic scores for requests and upgrades.
    • Generates queries that identify critical substations and explain how those scores affect upgrade decisions.
  2. Review the agent’s response.

    Your agent should:

    • Create a new model/graph.py file that builds a graph of the energy grid transmission network and computes centrality scores for each substation.
    • Update queries.py to include a centrality_report() function that summarizes the centrality scores and their implications for pending datacenter requests.
    • Provide a chat summary explaining the changes made, validation steps, and a plain-English summary of the results.
    Click to expand the sample response from /rai-graph-analysis

    Changes made:

    • Added graph.py with an undirected weighted Graph over Substation, using active TransmissionLine edges weighted by capacity_mw.
    • Added Substation.centrality_score and Substation.network_weight = 1.0 + centrality_score.
    • Imported the graph extension from __init__.py so the graph-derived properties load with the model.
    • Added centrality_report() in queries.py, and made the script run that report directly.

    Validation:

    • python queries.py completed successfully.
    • I did not modify schema.py, sources.py, or rules.py.

    Plain-English Summary

    The structural hubs are Dallas-Fort Worth, Houston Ship Channel, and San Antonio Metro, followed by Waco Gateway and Austin Energy. Dallas-Fort Worth is the strongest hub with centrality 0.5245, then Houston at 0.4776, and San Antonio at 0.3854.

    Those hubs are not currently the constrained substations in the report. The top centrality substations all showed is_constrained = False, with meaningful available headroom: Dallas-Fort Worth has 500 MW, Houston has 550 MW, and San Antonio has 420 MW.

    High-revenue data center demand is clustering mostly at the hubs. Dallas-Fort Worth alone has 3 pending requests totaling 1100 MW and $213M expected annual revenue. Houston adds $112.5M, and San Antonio adds $91.35M. The main peripheral pending requests are smaller: Midland-Permian at $28.8M and Corpus Christi Coast at $21.75M.

Find optimal actions with prescriptive reasoning

Section titled “Find optimal actions with prescriptive reasoning”

The prescriptive reasoning skills enable your agent to formulate an optimization problem, run a solver, and explain the results. Use them when you need to choose the best set of actions under constraints, such as which datacenter requests to approve and which upgrades to fund under a fixed budget.

Follow these steps to have your agent formulate and solve a prescriptive optimization problem.

  1. Invoke the prescriptive reasoning skills.

    Use a detailed prompt that defines the candidate set, decision variables, constraints, objective, scenario sweep, and expected outputs.

    For example, the following prompt is copied from the sample output in the /rai-discovery section and uses the rules and graph scores from the prior steps to solve an approval and upgrade optimization:

    /rai-prescriptive-problem-formulation + /rai-prescriptive-solver-management + /rai-prescriptive-results-interpretation
    Project context:
    - RAI tutorial project at workspace root. `model/schema.py` + `model/sources.py` define the base ontology.
    - `model/rules.py` provides DataCenterRequest.is_eligible / .expected_annual_revenue, Substation.available_headroom_mw, SubstationUpgrade.is_high_value, etc.
    - `model/graph.py` provides Substation.centrality_score and Substation.network_weight.
    Task: Formulate, solve, and interpret a portfolio decision that picks which eligible DataCenterRequests to approve and which SubstationUpgrades to fund under a budget.
    1. Create `model/optimization.py` with the prescriptive formulation:
    - Binary decision: `DataCenterRequest.approve` on each eligible request (filter by is_eligible).
    - Binary decision: `SubstationUpgrade.fund` on every upgrade.
    - Capacity constraint per Substation: sum(approve * requested_mw of requests targeting that substation) <= available_headroom_mw + sum(fund * capacity_increase_mw of upgrades at that substation).
    - Budget constraint (global): sum(fund * cost_million) <= BUDGET_MILLION (parameter — see below).
    - Objective: maximize sum over approved requests of (expected_annual_revenue * network_weight of target substation). Centrality-weighting biases approvals toward hubs.
    - Expose BUDGET_MILLION as a module-level parameter that can be set before solving.
    2. Create a new project-root file `portfolio_decision.py` that:
    - Solves the problem at three budget levels: $50M, $150M, $300M.
    - For each budget, extracts: list of approved requests (id, name, requested_mw, expected_annual_revenue, target substation, centrality), list of funded upgrades (id, substation, capacity_increase_mw, cost_million), total expected annual revenue, total upgrade spend, number of constrained substations relieved.
    - Prints a side-by-side comparison table across the three budgets.
    3. After running, write a plain-English executive summary covering:
    - Which datacenter requests should be approved at each budget level and why.
    - Which upgrades should be funded at each budget level and why (cost-efficiency vs. network position).
    - The marginal return: how much extra revenue each additional $100M of budget unlocks.
    - The recommended budget level and the reasoning behind it.
    Only add files as specified; do not modify schema.py, sources.py, rules.py, or graph.py.
    • Creates model/optimization.py to define the approval and funding decisions, constraints, and objective.
    • Uses the rules and graph properties from the earlier steps as inputs to the optimization.
    • Creates portfolio_decision.py to solve the model at three budget levels and compare the results side by side.
    • Produces a plain-English summary of which requests and upgrades are recommended at each budget level and why.
  2. Review the agent’s response.

    Your agent will formulate the optimization problem, run the solver, and summarize how the recommended approvals and upgrades change across budget levels. It may add solver code, output CSVs, or helper queries to the project.

    Click to expand the sample response from the prescriptive reasoning skills

    All three scenarios solved to OPTIMAL.

    Key results:

    BudgetApproved requestsFunded upgradesRevenueSpendRelieved substations
    $50MDC-004, DC-001, DC-006, DC-007, DC-010, DC-009UPG-006$309.9M$45.0M1
    $150MDC-004, DC-001, DC-002, DC-006, DC-008, DC-010, DC-009UPG-002, UPG-007$376.4M$120.0M2
    $300MDC-004, DC-003, DC-001, DC-002, DC-006, DC-007, DC-008, DC-010, DC-009UPG-003, UPG-002, UPG-006, UPG-007$498.2M$275.0M4

    Executive readout: $300M is the recommended tested budget level. It unlocks the highest expected annual revenue, funds the network-critical Dallas/Fort Worth and Houston upgrades plus strong capacity-per-dollar upgrades, and the second budget step has the stronger marginal return: $81.2M per extra $100M versus $66.5M from $50M to $150M.

Our project templates provide more examples of semantic models and reasoning in action.

Here’s a selection of good follow-up examples to explore:

To learn more about PyRel and all of RelationalAI’s features, continue reading with the following resources: