AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Rai Prescriptive Problem Formulation

skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation · by RelationalAI

Formulates optimization and constraint satisfaction problems from ontology models — decision variables, constraints, objectives, and common patterns. Use for optimization and constraint-satisfaction tasks — building, reviewing, debugging, or relaxing an over-constrained formulation to resolve a reported conflict / IIS; then solve it with rai-prescriptive-solver-management. Not for interpreting or…

No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-relationalai-rai-agent-skills-rai-prescriptive-problem-formulation)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Rai Prescriptive Problem Formulation? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Problem Formulation

> Requires relationalai>=1.11.0. The dual-guided multi-objective methods in this skill read solver sensitivity via solve("highs", sensitivity=True) (shadow prices as exact frontier slopes); earlier versions reject the request at the solver and omit the dual fields on solve_info(). See rai-setup.

Summary

What: Optimization formulation — decision variables, constraints, objectives, and common problem patterns. Assumes a problem has already been selected via discovery.

When to use:

  • Formulating variables, constraints, and objectives for a selected problem
  • Reviewing or validating an existing formulation
  • Translating business requirements into mathematical formulation
  • Debugging formulations that would produce trivial or infeasible solutions (missing constraints, conflicting bounds, wrong aggregation scope)
  • Choosing between variable types (continuous, integer, binary)
  • Designing multi-concept coordination (flow networks, selection + quantity)

When NOT to use:

  • Post-solve diagnosis of solutions that look wrong (all zeros, infeasible status, concentrated values) — see rai-prescriptive-results-interpretation
  • Question discovery (what can this ontology answer, reasoner classification) — see rai-discovery
  • PyRel syntax (imports, types, property patterns, stdlib) — see rai-pyrel-coding
  • Ontology modeling or model enrichment (concept design, gap classification) — see rai-ontology-design
  • Solver execution and diagnostics (solver selection, parameters, numerical stability) — see rai-prescriptive-solver-management
  • Aggregation syntax (count/sum/per patterns) — see rai-querying

Overview:

  1. Ground in the base ontology via relationalai.semantics.inspect.schema(model) — concepts, properties, types, relationships you're about to reference
  2. Define decision variables (type, bounds, scope, naming)
  3. Define constraints (forcing, capacity, balance, linking; validate interactions)
  4. Define objective (direction, coefficients, multi-component handling)
  5. Validate the complete formulation (structure, completeness, feasibility, data) — includes pre-solver audit that decision variables / constraints / objectives registered correctly
  6. Solve and refine — diagnose surprising results via targeted display(ref) / verify / solve_info, simplify once correct, re-solve
  7. Post-solve refinement — present results, surface user reactions, iterate on the formulation (see Workflow Step 7)

Quick Reference — Problem API

from relationalai.semantics.reasoners.prescriptive import Problem
from relationalai.semantics.std import aggregates as aggs

# Problem(model, Float) for MIP-style; Problem(model, Integer) for CSP-style (see [csp-formulation.md](references/csp-formulation.md)).
problem = Problem(model, Float)

# Decision variable — prefer scoped form (where=[...]) over unscoped.
# Capture the returned ref for targeted diagnostics later (model.select(x_flow.name, x_flow.lower, x_flow.upper).to_df(), etc).
x_flow = problem.solve_for(
    Lane.flow,
    where=[Lane.active],
    name=["item_id", "src_id"],
    lower=0.0,
    type="cont",
)

# Constraint — capture the ref to inspect just this constraint's grounded form
cap = problem.satisfy(
    model.require(
        aggs.sum(Lane.flow).per(Source).where(Lane.from_source(Source)) =1.13*) enum members, rendered by member name; on 1.12.0 itself pass the Python-side string `Member.name` instead (a member fails type inference there). |
| `satisfy` | `(expr, name=, keyed_by=)` | Add constraint. Returns `ProblemConstraint` — capture this ref to inspect the constraint's grounded form via `problem.display(ref)`. `keyed_by={"key": Concept}` declares the family's grounding keys as identifying entity back-pointers for post-solve readback — see `references/constraint-formulation.md` > Declaring constraint keys for post-solve readback. |
| `minimize` | `(expr, name=)` | Set minimization objective. Returns `ProblemObjective` — capture for targeted `display(ref)`. |
| `maximize` | `(expr, name=)` | Set maximization objective. Returns `ProblemObjective` — capture for targeted `display(ref)`. |
| `solve` | `(solver, time_limit_sec=, print_format=, ...)` | Execute solve. Solvers: `"highs"`, `"minizinc"`, `"ipopt"`, `"gurobi"`. `print_format` (`"moi"`, `"latex"`, `"mof"`, `"lp"`, `"mps"`, `"nl"`) populates `solve_info().printed_model` |
| `solve_info` | `()` | Post-solve summary (`termination_status`, `objective_value`, `solve_time_sec`, `num_points`, `solver_version`, `error`, `printed_model`). Has `.display()` method. |
| `verify` | `(*fragments)` | Post-solve check that the returned solution satisfies the original `Fragment`s at IC strictness (tighter than solver tolerance). Pass the original `model.require(...)` values, not `ProblemConstraint` refs. |
| `display` | `(part=None, *, where=None, limit=None, print_output=True)` | Print materialized formulation. `display()` for everything; `display(ref)` for one constraint or objective; `display(ref, where=)` scopes to filter-matching rows (`where=` requires `part`); `display(..., limit=N)` caps each table at top-N rows by `.name`. Variable subconcepts raise — query rows via `model.select(var.name, var.lower, var.upper).to_df()`. |
| `num_variables` / `num_constraints` / `num_min_objectives` / `num_max_objectives` | `()` | Engine-queryable counts; usable inside `model.require(...)` to assert formulation cardinality before solve |
| `Variable.values` | `(sol_index, value_ref)` | Property on `ProblemVariable`. Extracts solution values at `sol_index` (0-based), binding each value to `value_ref` (a `Float.ref()` or `Integer.ref()`). Use inside `model.select(...).where(var.values(sol_index, value_ref))`. Primary pattern for `populate=False` workflows. |
| `problem.variables` / `problem.constraints` / `problem.objectives` | (attributes) | Lists of registered refs in declaration order — iterate to walk an unfamiliar Problem |

---

## Formulation Workflow

**Interaction mode:** Before starting, ask the user which mode they prefer:
- **Guided** — present your proposed variables, constraints, and objective at each step and confirm before proceeding. Best when the user has domain context to share — problem framing involves subjective judgment (what's a hard constraint vs. a soft goal, how to scope variables, which business rules matter most).
- **One-shot** — produce the best formulation you can in a single pass. Best when the user wants speed and will review/iterate after.

After a question is selected (from question discovery) and the ontology is enriched (if needed), build the formulation in this order:

### Step 1: Ground in the base ontology

Prescriptive formulations reference concepts, properties, and relationships from an existing model. Before writing `solve_for` / `satisfy` / `minimize` / `maximize`, confirm every name and type you're about to use against the real schema:

```python
from relationalai.semantics import inspect

schema = inspect.schema(model)

# Every concept referenced in the formulation
for name in referenced_concepts:
    assert name in schema, f"Concept {name} not in model"
    props = schema[name].properties
    # surface any properties used in bounds, constraints, or the objective

Catches two silent-failure modes that account for most prescriptive errors:

  1. Hallucinated surfaceCustomer.tier in a constraint when the real property is Customer.category. Solver happily runs on wrong variables and returns nonsense.
  2. Wrong-type inference — using an Integer property as if it were Float (or vice versa) when the type now propagates from TableSchema. Silent coercion masks incorrect bound derivation.

Reuse upstream derived surface. If rai-rules-authoring or rai-graph-analysis ran earlier in the same session, the subtypes and properties they wrote back (classification subtypes, centrality scores, derived flags) are already in the model — they show up in inspect.schema(model). Reference them directly in solve_for / satisfy / minimize / maximize rather than re-deriving their logic in the formulation. See [examples/chainedrulesprescriptive.py](examples/chainedrulesprescriptive.py).

Confirm modeled dimensions. If the problem spans periods/scenarios/categories, that dimension must exist in the ontology — as a Concept (Week, Period, Scenario), as an Integer reference scoped via std.common.range(), or, for a small closed vocabulary, as a model.Enum (requires relationalai>=1.12; enum-indexed solve_for creates one binary per (entity, member) — see references/csp-formulation.md § Enum-indexed decision) — before Step 3. Constraints over the dimension are then a single declarative expression that quantifies automatically. Never plan to iterate over the dimension in Python or enumerate its values by hand; both bypass the engine. See rai-ontology-design if enrichment is needed.

When to skip: this step is cheap but not free. Skip on small greenfield models or one-shot formulations where the model fits in a single code block you just wrote.

Step 2: Define Variables

What decisions are being made? What can the solver control?

  • Start with the base model context — examine concepts, properties, relationships
  • Identify the primary decision entity first, then auxiliary/aggregation variables
  • Choose variable types (continuous/integer/binary) and set bounds from data
  • For minimize objectives, include a slack/unmet variable to avoid infeasibility

See [variable-formulation.md](references/variable-formulation.md) for context integration, variable principles, and advanced patterns (types, bounds, entity creation, slack, parametric/time-indexed).

Step 3: Define Constraints

What rules must the solution satisfy?

  • Start with model structure + user goals
  • For multi-period or recurrence patterns (inventory balance, sequencing, cumulative state), use declarative quantification: Concept.ref() adjacency joins (w_prev.num == w.num - 1) or where=[t == std.common.range(start, stop)] — single satisfy() call grounds across all periods. See [constraint-formulation.md](references/constraint-formulation.md) > Temporal recurrence and [examples/multiperiodflowconservation.py](examples/multiperiodflowconservation.py).
  • Add forcing constraints first — these prevent trivial zero solutions for minimize objectives
  • Add capacity/resource constraints from data properties
  • Add flow conservation if the model has network structure
  • Derive parameters from data ranges, not arbitrary values

Step 4: Define Objective(s)

What are we optimizing?

  • Start with user's stated goal — map business language to minimize/maximize. See [objective-formulation.md](references/objective-formulation.md) for direction, multi-component objectives, and penalty terms
  • Reference defined variables and data properties in the expression
  • Check for trivial solution risk: "If all variables = 0, are all constraints satisfied?" If yes, Step 3 needs a forcing constraint.
  • Competing objectives? If the formulation has a penalty term bundling two concerns (cost + penalty×slack), or a constraint that represents a competing goal (return ≥ threshold), consider whether the user wants to explore the tradeoff rather than fix a single point. See [multi-objective-formulation.md](references/multi-objective-formulation.md) for the epsilon constraint approach.
  • Parameter sensitivity / what-if? If key constraints use fixed values that could vary (budget, demand, service level), use the Scenario Concept pattern — parameterize the constraint, index the decision variable by Scenario, and solve all scenarios in a single solve. This keeps results in the ontology and avoids manual re-solve loops. See [scenario-analysis.md](references/scenario-analysis.md).

Step 5: Validate

Is the formulation complete and correct?

  • Every variable appears in at least one constraint or the objective
  • Every constraint references at least one decision variable
  • Join paths in .where() clauses connect to actual data
  • Bounds are consistent (lower subconcept for each decision variable). They appear in inspect.schema(model).concepts`:
from relationalai.semantics import inspect

schema = inspect.schema(model)
variables   = [c for c in schema.concepts if "Variable" in c.extends]
constraints = [c for c in schema.concepts if c.name == "Constraint" or "Constraint" in c.extends]
objectives  = [c for c in schema.concepts if c.name == "Objective"  or "Objective"  in c.extends]

# Confirm one Variable_ per solve_for call, one Constraint_ per satisfy,
# one Objective_ per minimize/maximize.

(b) Binding cardinality. Registration does NOT mean the variable binds to any rows. A solve_for(..., where=[always_false]) still registers a Variable_ subconcept but has zero bindings — the solver will run on an empty decision set. Check each Variable_ for non-empty binding:

for var_concept in variables:
    resolved = model.concept_index[var_concept.name]
    n = len(model.select(resolved).to_df())
    if n == 0:
        # The where= clause excluded every row. Fix the predicate
        # (wrong property name, wrong threshold, missing join) and re-check.
        raise ValueError(f"{var_concept.name} has 0 bindings")

(c) Coefficient presence. Properties referenced as objective or constraint coefficients must be populated by model.define(...) — an unbound coefficient Property has no tuples, so the join produces no row and the term doesn't appear in the formulation (PyRel relational semantics). The solver returns OPTIMAL with a vacuous objective. Distinct from (b): (b) catches empty decision-variable scope, (c) catches unbound coefficient data. Both surface as OPTIMAL with obj=0.

for coef_prop in objective_coefficient_properties:
    n = len(model.select(coef_prop).to_df())
    if n == 0:
        raise ValueError(f"Coefficient {coef_prop} has no tuples — objective term won't ground")

(d) Constraint binding cardinality. Capture each satisfy() return value at declaration time, then verify the constraint grounded on the expected number of groupings before solving. Distinct from (b)/(c): (b) catches empty variable scope from a where= predicate, (c) catches unbound coefficients, (d) catches per-grouping bodies that didn't ground because a referenced bound was empty for some entities — under PyRel relational semantics an empty body produces no row, so the constraint applies to fewer entities than the .per(Entity) syntax suggests, and OPTIMAL returns with the entities that got no row effectively unconstrained.

cap_constr = problem.satisfy(model.require(usage  Targeted Inspection.

**Simplify once correct:**
- Static parameters over dynamic calculations
- Objective terms for goals; constraints for hard requirements
- Group-level constraints over pairwise/granular combinations

Loop until the result is correct, fast enough, and defensible enough to take to Step 7. See [diagnostic-workflow.md](references/diagnostic-workflow.md) for failure-mode-by-failure-mode guidance and [formulation-simplification.md](references/formulation-simplification.md) for the simplification patterns in depth.

### Step 7: Present, React, Refine
Is the formulation complete — including constraints the user couldn't articulate upfront?
- Solve and present the result to the user (see Constraint Elicitation > Post-Solve: Iterative Refinement)
- Use the result as an elicitation tool to surface latent preferences
- Disambiguate rejections into constraint types, add them, re-solve
- Repeat until the user accepts or feasibility pressure forces prioritization

Steps 1-6 produce the best formulation you can build from what the user has told you. Step 7 discovers what they couldn't tell you until they saw a concrete result. Most real-world formulations require at least one pass through Step 7.

---

## Formul

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [RelationalAI](https://github.com/RelationalAI)
- **Source:** [RelationalAI/rai-agent-skills](https://github.com/RelationalAI/rai-agent-skills)
- **License:** Apache-2.0
- **Homepage:** https://relational.ai

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.