|
| 1 | +--- |
| 2 | +title: "Copy-paste prompts for Misata: agents, schemas and plain English" |
| 3 | +description: "Ready-to-paste prompts for generating synthetic data with Misata, whether you are driving it from Claude, Cursor or ChatGPT, writing a schema directly, or starting from a sentence. Every prompt here was run before publishing." |
| 4 | +--- |
| 5 | + |
| 6 | +# Prompts |
| 7 | + |
| 8 | +Three ways to reach the same engine, in descending order of how much you can |
| 9 | +trust the result without checking it. |
| 10 | + |
| 11 | +```diagram |
| 12 | +# Reliability, and where the checking happens |
| 13 | +
|
| 14 | + you write the schema an agent writes it plain English |
| 15 | + ▏ ▏ ▏ |
| 16 | + exact, every key exact once emitted, approximate, and |
| 17 | + under your control agent may guess silently so |
| 18 | + ▏ ▏ ▏ |
| 19 | + ▼ ▼ ▼ |
| 20 | + feasibility refuses a contradiction in all three; only the first two |
| 21 | + guarantee that what you asked for is what was asked of the engine |
| 22 | +``` |
| 23 | + |
| 24 | +Every prompt on this page was executed against the engine before publishing, |
| 25 | +and the plain-English section says exactly where that path breaks. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## 1. Driving Misata from an agent |
| 30 | + |
| 31 | +The most reliable way to get a schema you did not want to write by hand. The |
| 32 | +agent produces a schema, the engine validates it, and feasibility refuses |
| 33 | +anything contradictory, so a bad guess fails loudly instead of quietly. |
| 34 | + |
| 35 | +Paste this into Claude, Cursor, ChatGPT or any coding agent, then add your own |
| 36 | +last paragraph. |
| 37 | + |
| 38 | +```text |
| 39 | +You are writing a schema for Misata, a declarative synthetic data engine |
| 40 | +(pip install misata). Read https://misata.studio/llms.txt for the full |
| 41 | +documentation index; each page is also available as raw Markdown by appending |
| 42 | +.md to its URL. |
| 43 | +
|
| 44 | +Rules: |
| 45 | +- Output one Python dict, built with |
| 46 | + schema = misata.from_dict_schema(<the dict>, seed=42) |
| 47 | + tables = misata.generate_from_schema(schema) |
| 48 | + Note generate_from_schema takes a SchemaConfig and takes no seed of its own; |
| 49 | + the seed belongs on from_dict_schema. |
| 50 | +- Top-level keys are table names. Each table takes "__rows__" plus its columns. |
| 51 | +- Column types: integer, float, string, text, date, datetime, boolean, |
| 52 | + foreign_key. A foreign key is |
| 53 | + {"type": "foreign_key", "foreign_key": {"table": "parent", "column": "id"}}. |
| 54 | +- Schema-level declarations are __dunder__ keys beside the tables: |
| 55 | + __outcome_curves__ (an aggregate over time, hit exactly) |
| 56 | + __rate_curves__ (a rate over time, hit exactly) |
| 57 | + __group_shares__ (exact shares of a measure across a category) |
| 58 | + __joint_distributions__ (several margins holding at once) |
| 59 | + __waterfalls__ (movements reconciling to declared balances) |
| 60 | + __stock_flows__ (closing = opening + received - shipped, per unit) |
| 61 | + __lifecycles__ (a state machine, with legal transitions) |
| 62 | + __missingness__ (why values are missing, conditionally) |
| 63 | + __constraints__, __dag_edges__, __closures__, __graph_motifs__ |
| 64 | +- Prefer declaring the aggregate over inventing per-row values. If the user |
| 65 | + says "revenue grows to 200k", that is an __outcome_curves__ declaration, |
| 66 | + not a min/max on a column. |
| 67 | +- Do not invent declaration names. If you are unsure a key exists, fetch |
| 68 | + https://misata.studio/docs/reference/declarations.md and check. |
| 69 | +- Always pass a seed, so the run is reproducible. |
| 70 | +
|
| 71 | +Now write a schema for: <describe your dataset here> |
| 72 | +``` |
| 73 | + |
| 74 | +Why this prompt works: it points the agent at a machine-readable index rather |
| 75 | +than hoping the model remembers the API, it names the declarations explicitly so |
| 76 | +the model does not invent plausible ones, and it tells the model to reach for a |
| 77 | +declaration rather than fake the number per row, which is the single most common |
| 78 | +mistake. |
| 79 | + |
| 80 | +### Shorter version, for a model that already has the docs |
| 81 | + |
| 82 | +```text |
| 83 | +Write a Misata schema (misata.from_dict_schema(..., seed=42) then |
| 84 | +misata.generate_from_schema(schema)) for: |
| 85 | +<your dataset>. |
| 86 | +
|
| 87 | +Use __outcome_curves__ for any "grows to X" aggregate, __group_shares__ for any |
| 88 | +"A is 40% of B", and foreign_key columns for every relationship. Check key names |
| 89 | +against https://misata.studio/docs/reference/declarations.md before answering. |
| 90 | +``` |
| 91 | + |
| 92 | +### Giving an agent the tools directly |
| 93 | + |
| 94 | +Misata ships an MCP server, so an agent can generate data itself rather than |
| 95 | +writing code you then run: |
| 96 | + |
| 97 | +```bash |
| 98 | +pip install "misata[mcp]" |
| 99 | +``` |
| 100 | + |
| 101 | +See the [MCP guide](guides/mcp.md). Once connected, this is enough: |
| 102 | + |
| 103 | +```text |
| 104 | +Use the Misata MCP server to generate a 5-table ecommerce dataset with 2,000 |
| 105 | +buyers and 20,000 orders where GMV grows from $80k in January 2026 to $260k in |
| 106 | +December 2026, then show me the integrity verification. |
| 107 | +``` |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## 2. Schema recipes, ready to paste |
| 112 | + |
| 113 | +These run as written. |
| 114 | + |
| 115 | +### A SaaS book with revenue that hits its targets |
| 116 | + |
| 117 | +```python |
| 118 | +import misata |
| 119 | + |
| 120 | +schema = misata.from_dict_schema({ |
| 121 | + "customers": { |
| 122 | + "__rows__": 800, |
| 123 | + "id": {"type": "integer", "primary_key": True}, |
| 124 | + "company": {"type": "text", "semantic": "company_name"}, |
| 125 | + "signed_up": {"type": "date"}, |
| 126 | + }, |
| 127 | + "invoices": { |
| 128 | + "__rows__": 6000, |
| 129 | + "id": {"type": "integer", "primary_key": True}, |
| 130 | + "customer_id": {"type": "foreign_key", |
| 131 | + "foreign_key": {"table": "customers", "column": "id"}}, |
| 132 | + "issued_at": {"type": "date"}, |
| 133 | + "amount": {"type": "float", "min": 50, "max": 4000}, |
| 134 | + "plan": {"type": "string", "enum": ["Starter", "Pro", "Enterprise"]}, |
| 135 | + }, |
| 136 | + "__outcome_curves__": [{ |
| 137 | + "table": "invoices", "column": "amount", |
| 138 | + "time_column": "issued_at", "time_unit": "month", |
| 139 | + "value_mode": "absolute", |
| 140 | + "curve_points": [{"date": "2026-01-01", "target_value": 50000}, |
| 141 | + {"date": "2026-12-01", "target_value": 200000}], |
| 142 | + }], |
| 143 | + "__group_shares__": [{ |
| 144 | + "table": "invoices", "measure": "amount", "group_column": "plan", |
| 145 | + "shares": {"Starter": 0.2, "Pro": 0.5, "Enterprise": 0.3}, |
| 146 | + }], |
| 147 | +}, seed=42) |
| 148 | +tables = misata.generate_from_schema(schema) |
| 149 | +``` |
| 150 | + |
| 151 | +Every month's invoice total equals its declared target, and within every total |
| 152 | +the three plans split 20/50/30, exactly. |
| 153 | + |
| 154 | +### An MRR waterfall that reconciles |
| 155 | + |
| 156 | +```python |
| 157 | +schema = misata.from_dict_schema({ |
| 158 | + "mrr_movements": { |
| 159 | + "__rows__": 900, |
| 160 | + "period": {"type": "string"}, |
| 161 | + "movement_type": {"type": "string"}, |
| 162 | + "amount": {"type": "float"}, |
| 163 | + }, |
| 164 | + "__waterfalls__": [{ |
| 165 | + "table": "mrr_movements", "starting_value": 100000, |
| 166 | + "points": [{"period": f"2026-{m:02d}", "ending_value": 100000 + m * 6000} |
| 167 | + for m in range(1, 7)], |
| 168 | + "inflow_shares": {"new": 0.7, "expansion": 0.3}, |
| 169 | + "outflow_shares": {"churn": 1.0}, |
| 170 | + }], |
| 171 | +}, seed=42) |
| 172 | +tables = misata.generate_from_schema(schema) |
| 173 | +``` |
| 174 | + |
| 175 | +### An order lifecycle nobody can contradict |
| 176 | + |
| 177 | +```python |
| 178 | +schema = misata.from_dict_schema({ |
| 179 | + "orders": { |
| 180 | + "__rows__": 5000, |
| 181 | + "id": {"type": "integer", "primary_key": True}, |
| 182 | + "status": {"type": "string"}, |
| 183 | + "placed_at": {"type": "date"}, |
| 184 | + }, |
| 185 | + "__lifecycles__": [{ |
| 186 | + "name": "order_flow", "table": "orders", |
| 187 | + "state_column": "status", "start_column": "placed_at", |
| 188 | + "states": [{"name": "placed"}, {"name": "paid"}, {"name": "shipped"}, |
| 189 | + {"name": "delivered"}, {"name": "refunded", "terminal": True}], |
| 190 | + "transitions": [["placed", "paid"], ["paid", "shipped"], |
| 191 | + ["shipped", "delivered"], ["delivered", "refunded"]], |
| 192 | + "initial": "placed", |
| 193 | + }], |
| 194 | +}, seed=42) |
| 195 | +tables = misata.generate_from_schema(schema) |
| 196 | +``` |
| 197 | + |
| 198 | +### Dirty data on purpose, for testing a cleaning step |
| 199 | + |
| 200 | +```python |
| 201 | +schema = misata.from_dict_schema({ |
| 202 | + "contacts": { |
| 203 | + "__rows__": 5000, |
| 204 | + "id": {"type": "integer", "primary_key": True}, |
| 205 | + "name": {"type": "text", "semantic": "person_name"}, |
| 206 | + "city": {"type": "string", "enum": ["Berlin", "Lisbon", "Oslo", "Porto"]}, |
| 207 | + "notes": {"type": "text"}, |
| 208 | + "is_active": {"type": "boolean"}, |
| 209 | + }, |
| 210 | + "__typos__": [{"table": "contacts", "column": "city", "count": 120}], |
| 211 | + "__duplicates__": [{"table": "contacts", "count": 60}], |
| 212 | + "__missingness__": [{ |
| 213 | + "table": "contacts", "column": "notes", |
| 214 | + "rate": 0.75, "else_rate": 0.05, |
| 215 | + "when_column": "is_active", "when_op": "==", "when_value": False, |
| 216 | + }], |
| 217 | +}, seed=42) |
| 218 | +tables = misata.generate_from_schema(schema) |
| 219 | +``` |
| 220 | + |
| 221 | +Exactly 120 corrupted city values and exactly 60 duplicate rows, so your |
| 222 | +cleaning step has a known number to find and your test can assert it. |
| 223 | + |
| 224 | +### Seed a real database |
| 225 | + |
| 226 | +```bash |
| 227 | +misata seed "postgresql://user:pass@localhost:5432/mydb" --rows 5000 |
| 228 | +``` |
| 229 | + |
| 230 | +It reads the schema from the database, inserts parents before children, and |
| 231 | +verifies every foreign key afterwards. It plans by default; add `--apply` once |
| 232 | +the plan looks right. See [Database seeding in Python](guides/database-seeding-python.md). |
| 233 | + |
| 234 | +--- |
| 235 | + |
| 236 | +## 3. Plain English |
| 237 | + |
| 238 | +`misata.generate("...")` parses a sentence into a schema. It is the fastest way |
| 239 | +to something on screen and the least precise, so use it to start and then edit |
| 240 | +the schema it produced. |
| 241 | + |
| 242 | +### What works |
| 243 | + |
| 244 | +Nouns with counts, in one clause: |
| 245 | + |
| 246 | +```python |
| 247 | +misata.generate("A SaaS company with 800 customers and 1200 subscriptions", seed=11) |
| 248 | +``` |
| 249 | + |
| 250 | +Put a rate in its own sentence rather than in a relative clause: |
| 251 | + |
| 252 | +```python |
| 253 | +misata.generate( |
| 254 | + "A payments processor with 500 merchants and 20000 transactions. " |
| 255 | + "The dispute rate is 1.5%.", |
| 256 | + seed=11, |
| 257 | +) |
| 258 | +``` |
| 259 | + |
| 260 | +### What does not, measured |
| 261 | + |
| 262 | +These are real results from the current parser, not cautions in principle. |
| 263 | + |
| 264 | +- **An entity can be dropped silently.** `"800 customers, 1200 subscriptions and |
| 265 | + 5000 invoices"` produces `customers` and `subscriptions` only. No warning, no |
| 266 | + invoices table. |
| 267 | +- **A relative clause can become a table.** `"...where 12% of admissions are |
| 268 | + readmissions within 30 days"` produces a table named `are_readmissions`. |
| 269 | + Splitting the rate into its own sentence avoids it. |
| 270 | +- **Row counts drift when a curve is in the same sentence.** Adding "where |
| 271 | + revenue grows from X to Y" to a sentence that also declares counts can leave a |
| 272 | + table at the default row count instead of the one you asked for. |
| 273 | + |
| 274 | +So: always check what you got before you trust it. |
| 275 | + |
| 276 | +```python |
| 277 | +tables = misata.generate("...", seed=11) |
| 278 | +print({name: (len(df), list(df.columns)) for name, df in tables.items()}) |
| 279 | +``` |
| 280 | + |
| 281 | +If a table is missing or a count is wrong, move to a dict schema. The plain |
| 282 | +English path is a draft; the schema is the specification. Sections 1 and 2 above |
| 283 | +exist because that is the honest split. |
| 284 | + |
| 285 | +The [canvas at misata.studio/try](https://misata.studio/try) shows the parsed |
| 286 | +schema visually before you generate, which is the fastest way to catch a |
| 287 | +misreading. |
| 288 | + |
| 289 | +--- |
| 290 | + |
| 291 | +## Next |
| 292 | + |
| 293 | +- [Declaration reference](reference/declarations.md), every property you can state |
| 294 | +- [Quick start](quickstart.md) |
| 295 | +- [Try it without installing](https://misata.studio/try) |
| 296 | +- [Ready-made datasets](https://misata.studio/datasets) |
| 297 | +- [What Misata does not do well yet](https://misata.studio/limitations) |
0 commit comments