# Authoring and Refining a Contract The contract is what ADI generates your agent's SQL from, and what that SQL is held to. It declares what your data means, which metrics exist, how they may be sliced, and what is out of bounds. Everything downstream depends on it. A few things are worth understanding before you start. Then authoring a contract has four steps: what to prepare before the run, what to ask for during it, how to test the result, and how to refine it afterwards. For setup and the install steps, see Getting Started. For the tool reference, see the Features page. ## What authoring controls, and why it needs your review ### Authoring is judgement; generation is a fixed rule Three steps happen, and only the last of them behaves the same way every time. **Authoring is done by your agent.** It reads ADI's authoring specification, looks at your schema and whatever context you give it, and drafts the contract bundle. That drafting is the work of the language model your agent runs. **Turning your question into a structured request is also your agent's model, in a smaller way.** It picks which metric, filters and grouping your question means, before ADI ever sees it. **Compiling that structured request into SQL is done by ADI, without a language model.** Given a contract and a structured request, ADI's compilation is deterministic: the same structured request against the same contract produces the same SQL, whichever model your agent runs. The contract is the boundary. Above it, your agent is exercising judgement. Whether an answer means what you think it means is a property of the contract: if a metric is defined on the wrong basis, or its name implies a filter the calculation does not apply, every query using it returns a figure that is wrong in the same way, however reliably it is generated. Accuracy is what authoring decides, which makes it the part you can improve. Below it, ADI is applying fixed rules to whatever structured request it is handed. Compilation is deterministic: within a fixed contract, the same structured request produces the same SQL and the same figures, for every session and every teammate working from it. Whether the same question produces the same structured request each time is a separate question, and it depends on your agent's model, not on ADI. **These two do not trade off.** Improving a contract makes the answers mean more without making them less stable: once a contract is fixed, there is no judgement left in the compile step, whatever the contract says. **Use your strongest available model for authoring.** Authoring is a one-off cost, so it is the wrong place to economise. Querying afterwards is cheap, and the compiled SQL does not depend on the model. As an illustration from an early test with Anthropic's models, on one dataset and an earlier version of the authoring Skill: Haiku did not produce a usable contract, reaching a clean validation only by deleting every join; Sonnet produced a complete and usable one; Opus produced the most complete, in the fewest validation rounds. In a more recent test, contracts authored with Opus and with Sonnet both passed our correctness checks, and no quality difference between the two was established. These are small comparisons rather than a rule. Whichever model you use, it does not author the same contract twice. > [!NOTE] > **ADI does not read your warehouse.** Server-side schema introspection and profiling are separate capabilities that are switched off on this tier, so your agent works from ADI's specification, your schema, and the context you give it. Where this guide suggests checking something against your data, that is your agent querying your warehouse directly, outside ADI. ### Two independent runs rarely agree, so review before you rely on either Ask two agents to author a contract over the same tables, with no other guidance, and you will get two different contracts. They will usually agree on the plain things: the tables, the main entities, the straightforward counts and sums. They diverge on the judgement calls. In our own testing over an identical dataset, independent runs differed on which metrics existed and what they were called, the grain declared for a table, whether a helper view was created to give it a clean key, which dimensions each metric could be grouped by, and whether the optional rules and policies documents were written at all. **What differs is coverage and definition, not the reliability of the answer.** In our reproducibility runs, wherever two of those contracts could both express the same question, they returned the same figure. Where two contracts define a metric differently, as two independently authored contracts over another dataset did for revenue, the figures differ by exactly that definition. One contract exposed a breakdown that another refused, because one author had allowed that group-by and the other had not. This follows from where the drafting happens: your agent's model is making the choices, and the schema does not settle them, so two careful runs can land differently without either being wrong. Two risks follow: a contract that quietly answers less than you need, and a contract whose definitions you have not yet reviewed and agreed. The four steps below deal with both. > [!NOTE] > **Reading the result is not part of the contract.** ADI governs the SQL, not the interpretation. A correct governed result can still be summarised wrongly, for example by adding together figures the contract declares cannot be added, such as amounts in different currencies. Check headline figures your agent reports back to you against the components it was given. ### Author once, then reuse A contract is meant to be authored carefully once and then reused, not written fresh each time you have a question. Once a contract exists, ADI applies it the same way to every request that comes through it. One reviewed contract shared across a team is what keeps definitions steady. Each fresh authoring is a new set of judgement calls, so re-authoring from scratch is a bootstrap step, not routine work. The effort belongs at the front: get the first contract right, review it, share it, and amend it as your needs change. ## Step 1: Prepare Most of the quality comes from what you bring to the authoring run. Your schema tells your agent what exists; only you can tell it what any of it means. **Write down the questions you want to ask.** Hand your agent that list, in plain language, before it starts. It aims the run at something, and it becomes the test you run in step 3. **Bring four documents.** Between them they carry almost all of the meaning a schema cannot: - **A metric glossary.** How each business term is defined: what counts as revenue, active, churned, acquired. - **A revenue-recognition policy.** Which order states count as realised revenue, and how cancellations, refunds, unpaid and pending orders, gift cards, tax and shipping are treated. This one decides more numbers than any other document, and almost no schema encodes it. - **A data dictionary.** Column meanings, status and enum code lists, and which timestamp is the business event rather than an ingest or update time. - **A sensitivity classification.** Which columns hold personal or sensitive data, and how you want each handled. Bring whatever subset you have, and ask your agent to record which ones were missing. Without a glossary and a revenue-recognition policy, every business definition becomes an inference from column names: revenue may quietly include cancelled or unpaid orders, and a count of new customers may be measured from a registration date rather than first activity. The resulting figures can be internally consistent and still not authoritative. Without a sensitivity classification, the policies document is written from column-name patterns alone, and it will miss the columns whose names do not advertise what they hold. **Give the answer for any term you already argue about.** If your team disagrees on how revenue is counted, say so up front and give the definition you want used. Otherwise your agent will pick one, and it may pick differently next time. **Flag the history the data does not explain.** Three classes of problem survive authoring because no schema can reveal them: - **Renaming and succession.** An entity that changed name, merged or was acquired appears as two unrelated values, and totals split silently across both. - **Partial coverage.** A column or category that only started being recorded part-way through the history will understate every earlier period. - **The period you are in.** If the latest month is still filling up, every trend ending on it bends downwards for no real reason. Tell your agent what you know, and ask it to check the coverage of each category against the full date range. Then have it act on the answer, either refusing the affected question through the contract or carrying a note on the metric that says what the figure does and does not include. ## Step 2: Author **Ask your agent to record every contestable decision, and treat that record as the point.** Ask for a short note per judgement call: what was decided, and on what basis. It makes the contract easier to review, because the reasoning sits beside it. More usefully, a written decision lets the next authoring run, by another agent or by the same one next month, land on the same answer instead of deciding again from scratch, as long as the record goes into that run. In our testing, independent runs converged on the same definitions when each was given the question list and a data dictionary and asked to decide these points up front. Five decisions are worth asking for by name, because they are the ones the data leaves genuinely open: - **Succession.** Does a renamed, merged or split entity roll up to its predecessor? - **Grain.** Does one row mean one business event, or can one event produce several rows? - **Additivity.** Which metrics may be summed across time and across dimensions, and which may not? - **The current period, and "latest".** Is the most recent period complete, and what date governs "as of"? - **Scope.** Which population does each metric cover, and which rows are excluded? Ask for these even where the answer looks obvious. Left to itself a run will settle them anyway, because nothing in the schema decides them, and a decision you never saw is one you cannot disagree with. **Have the metric list come from a source you can name.** One metric per term in your glossary, not a set assembled from whatever looked interesting in the schema. Where you had no glossary entry for something, ask for that recorded as an assumption in the metric's notes. That is what makes the metric set repeatable rather than a matter of taste. It is also one of the places the independent runs differed. **Have the vocabulary use your team's own words.** The vocabulary document maps the words people use in questions to each metric and dimension. Ask your agent to list, for each metric, the terms your team actually uses for it, including abbreviations and the names used in reports, as well as its formal name. Decide deliberately what a bare word such as "count" or "revenue" should mean: map it to one metric on purpose, or leave it unmapped, which gives your agent a reason to ask which one was meant. This is what lets a question asked in business language resolve to the metric you meant, and the querying guide covers the asking side. **Pin the decisions that matter as checks inside the contract.** Where a judgement call could be silently undone later, your agent can record it as an assertion in the contract itself: a small piece of SQL and the result you expect, sitting beside the definition it protects. A choice of identity column is the clearest case, and so is a currency conversion that depends on a rate column having no gaps. Your agent can run that SQL against your warehouse directly whenever you want to confirm the assumption still holds. They cost little to write, and they turn a decision recorded in prose into one a machine can re-check. **Validate, and fix what validation reports instead of working around it.** Run the full set of checks, not a subset: each catches a different class of problem, and the one that compiles every metric catches what only the SQL compiler sees. Errors about the shape of a document point at the exact field; errors about how the documents fit together name the location in the message text instead. Treat a clean validation as the minimum bar for a usable contract, not as a sign that the contract is good. ## Step 3: Test the contract A contract that validates is not yet a contract you can rely on. Work through the following before you build on it. **Walk your question list, then push past it.** Have your agent take the list from step 1 and check each question against the draft, reporting answered, refused or not expressible. A refusal is a fine outcome at this stage; the case worth catching is a question the contract answers with a figure that does not mean what you asked. Then have it do the same for the questions you did not think to write down, because those are the ones that catch a missing breakdown: rankings and leaderboards, splits by each obvious categorical column, and trends over time. For each one, either confirm some metric allows that grouping or record a deliberate decision to leave it out, with the reason. **Lock the groupings deliberately rather than by default.** Restricting the groupings a metric allows makes the contract's behaviour easier to predict, and it can also close off a question the contract used to answer. A grouping you blocked on purpose is governance; one you blocked without noticing is a gap. **Read the metric list as a business reader, not as a reviewer of YAML.** Ask your agent to list every metric with its definition and any filters in plain English. Most real problems are visible at that level: a metric that does not mean what its name says, a missing breakdown, a definition you disagree with. **Check that every recorded decision is one you agree with.** Read the notes from step 2 beside the contract, not in place of it, since a summary can describe a bundle inaccurately. A decision you disagree with is cheap to change now and expensive to discover in a figure later. **Read every departure from your own documents.** Before you use the contract, open the decisions record your agent writes beside it (by default `./adi/DECISIONS.md`, next to `./adi/contract/`). Its first section, "Departures from your documents", lists every point where the contract departs from a document you supplied and every open question about one, each marked CONFIRM: answer each one, because the authoring Skill does not treat the contract as finished until you have. In our tests, with an earlier version of the authoring Skill, the authoring agent replaced three of the definitions it was given, in each of two runs, and recorded each change instead of asking. A replaced definition applies to every later query, and its figures look just as authoritative as yours. **Read the metric names that imply a filter.** A metric called "paid revenue" should carry the filter that makes it paid, and a note saying so. A name implying a narrower population than the calculation covers will mislead every reader of the result. **Confirm the declared key really identifies one row.** Each entity has to declare a key, so the question is whether that key is genuinely unique. Have your agent compare a row count against a distinct count on it. Where a table has no such key, the honest options are to build a view that supplies one, or to declare the closest natural combination and record in the metric notes that it is an analytical grain rather than a guaranteed unique key. What matters is that the choice is deliberate and written down. **If the contract matters enough, author it twice and compare the decisions.** Not the YAML, the decisions. Where two independent runs chose differently is where your data left the choice open, which tells you which definitions need your input rather than your agent's. One extra run turns the variation into information you can use. Keep whichever contract you then agree with. **Check a few numbers against something you already trust.** The authoring Skill asks you at the start for a few figures you already trust, each with its period and where it came from. Once the contract validates, your agent generates the governed query for each and runs it (or asks you to), and stops on any difference until you decide; if you have no such figures, it records that the check was not run. You can run the same check yourself for any figure you know independently. This is the most direct check of the contract against your actual data rather than against its own structure. Judge the result by whether it answers your questions and defines your terms correctly, not by how many metrics it has. ## Step 4: Refine Treat the first contract as a draft. Refining it is normal work, not a sign that something went wrong. **Challenge by asking, not by editing.** Put the question to your agent: why is revenue defined this way, what would it take to break this down by region, which of these metrics excludes cancelled orders. The answers tell you whether a decision was deliberate. **When a refusal looks unreasonable, treat it as a request.** A refusal carries the reason and, for most codes, the alternatives that are allowed. If the question is one you legitimately need, the fix is to amend the contract so the dimension is exposed, rather than to work around it. That is usually a small change. **Amend rather than re-author.** Ask your agent to load the existing bundle, make the specific change, re-validate, and record what changed and why. **Keep your question list with the contract and re-run it after any amendment.** A change that opens one question can quietly close another. ## Known traps These come out of our own testing and are worth checking specifically. **A clean validation does not mean the contract matches your data.** Validation checks the contract against itself: that it is well formed, that its documents agree, and that every metric compiles. It does not reach your data, so a contract can declare a column that no longer exists as declared and still validate cleanly. Only a check against your real schema finds that: running a query, or validation with schema metadata supplied. **Schema metadata in a format the server cannot read looks like a missing table.** To have validation check your real tables, your agent passes their metadata in `adi.validate`'s `schema_metadata` parameter. In our tests, a shorter format the server did not read failed as `physical_table_missing` ("does not match any inspected table") rather than saying the metadata was not understood. This format works: ```json { "tables": [ { "fqn": "analytics.main.orders", "table_catalog": "analytics", "table_schema": "main", "table_name": "orders", "columns": [ {"name": "order_id", "column_name": "order_id", "data_type": "INTEGER"}, {"name": "country", "column_name": "country", "data_type": "VARCHAR"} ] } ] } ``` Keep every key shown: the shorter form had only `catalog`, `schema` and `name` for the table and `name` and `type` for each column. With metadata the server can read, a column the contract declares but the table lacks is reported as an error. **A declared key that is not actually unique.** Where a table has no natural unique key it is tempting to declare the closest combination of columns. If that combination has duplicates, distinct counts undercount and joins can multiply rows. Build a view supplying a clean key when the data does not have one. **Several columns can identify the same thing, and the name will not tell you which is right.** A table that records who did something often carries more than one column that could answer it: the party who acted and the party who acted on their behalf, the business entity and a technical row key, the natural key and a surrogate. Every one of them compiles, validates and returns a plausible figure, and they can disagree by orders of magnitude, so the wrong choice survives a sanity check. Before your agent declares any distinct-count metric, have it compare the distinct counts of every candidate column and choose the one whose population matches the entity you mean, then record the chosen column, the rejected one and both counts in the metric's notes. A dictionary that calls a column the payer is describing a label, not a population. **The same metric name can mean two different things in two contracts.** Two contracts authored over the same tables can each carry a metric called, say, active customers and mean a different population by it. This matters when contracts are shared, compared or replaced: check the definition rather than the name before you reuse a figure from one bundle against another. **A rate that cannot be declared, and the two ways of composing it that go wrong.** ADI's declared ratio metrics take a specific shape, so some rates have to be composed by dividing one governed metric by another. Declaring the rate anyway, as a ratio whose built-in filter applies to both halves, yields a rate that is always 1. Composing it from two metrics whose populations are unrelated yields a figure that is a valid rate on neither. Composing a rate or share is correct when the numerator's population sits inside the denominator's, such as cancelled orders out of all orders. Composing an average needs both halves over the same population: merchandise sale units divided by all invoices, cancellations included, answers no real question. Say which basis you used in the note. **A definition whose two halves are on different bases.** If your own definition divides, say, merchandise revenue by all identified customers, a declared ratio filters both halves alike and so changes your definition. If that is the definition you mean, ask your agent to author the two halves as separate metrics, divide them downstream and list it as a departure for you to confirm; otherwise, choose one basis for both halves. **Some measures are not contract metrics at all.** Medians and other percentiles (including a median duration or latency), compound rates built from more than one division, and concentration indices such as Gini or a Herfindahl index have no metric type that covers them. Compute them downstream from governed metrics or from your own SQL, and resist approximating them with a sum or a ratio, which produces a figure that looks governed and is not the measure you asked for. **A rate with no minimum denominator.** A percentage over three records will top any ranking. Where a rate is meaningful only above some volume, record that in the metric's note and apply it when you read the results. **Filters in the request are lower case; filters inside the contract are upper case.** These are two different schemas and the difference is easy to miss. See the Troubleshooting guide. **Declare the engine and dialect pair the specification gives for your warehouse.** ADI emits SQL shaped for the pair the contract declares, not for wherever you run it. Not every engine has a dialect of the same name, so take the pair from the specification's own engine-to-dialect mapping rather than guessing. Declaring a dialect that does not exist fails validation; declaring a valid but wrong pair produces SQL that fails on your warehouse. See the Troubleshooting guide. **A contract can be made to validate by removing capability.** If validation keeps failing on a section, deleting that section will make it pass. In an early test of ours, a weaker model, unable to resolve a formatting error in the join definitions, deleted every join in the contract and validated cleanly, leaving a contract that could not connect any two tables. Stronger models fixed the formatting and kept the joins. If a fix makes a contract simpler, check what it can still answer. ## Where to go next - **Getting Started** covers installation, connecting, and authoring your first contract - **The Features page** documents all four MCP tools, the contract structure, and the full refusal set - **Beta Notes and Known Issues** carries the current limitations - **The Troubleshooting guide** covers the specific failures above - **The querying guide** covers asking questions of the finished contract and checking the answers - **Versions** says where to find the current plugin and Skill versions - **Legal and Data Use** sets out what not to use ADI for, including the data to keep out of a contract