This page is the published copy of the operating guide that ships with the tool
as
AGENTS.md — in the release archive and in
getsynq/synqcli. It is deliberately
denser than the rest of this section. Start at
Monitors as code for what the YAML declares, and
see the CLI reference for every flag.synqcli to declare data quality monitors, SQL tests and deployment rules as code. It gives the loop to follow, how to read the output, what each refusal means, and the mistakes that cost the most time. Follow it from the top.
synqcli deploy reconciles a workspace with a YAML file. You declare which monitors and tests belong on which assets, and deploy makes the workspace match. So it creates, it updates, and it deletes: it removes anything that the namespace of the file owns and the file no longer declares. If you remember this, you prevent most of the damage that this tool can do.
Two more facts control almost everything else (§ 3, § 6):
- The
id:of a check is its address. With anid:, you can edit the rest of the check. Without one, the content of the check is its identity, so some edits replace the check. - A check belongs to exactly one namespace. If a second config declares it,
deployrefuses. It does not merge.
1. The loop
deploy gets changes that they did not write.
Get the schema into the editor first. It is the field reference, and it has more authority than any prose:
2. Entity ids: resolve them, never guess them
A wrong entity id is the most common failure, and the CLI can help with it the least. A wrong id is not a typo that it can correct: the asset is not there. You can write an entity id in three forms.deploy resolves all three against the workspace before it deploys anything:
The first two forms are the same path. The third is the name of the table in the warehouse. It is matched only for ids that the first lookup did not find.
Resolution works only for an asset that the platform already knows, and only for a table, a view or an equivalent model.
- An id of the same asset under another integration (a Coalesce node or a dbt model) is accepted. It resolves to the warehouse table, or to its view if there is no table. If you write such an id as a full
::path,deployprints the replacement as<id> → <path>. - Other known entities (a dashboard, a job) cannot be monitored and fail with the message below.
- Run
synqcli export --source all out.yamland read theentities[].idvalues. They are already in the form that the config accepts. - The page of the asset in the web app shows the same path.
Reading the failure
auth status before you check the spelling.
--dry-run, and no plan is printed. Fix every id before you expect a plan.
3. The namespace is the reconcile boundary
namespace groups checks, so that different teams reconcile independently. It is the most important field in the file:
- Deletes are limited to it.
deployremoves checks that this namespace owns and that the file no longer declares. It never changes the checks of another namespace. - A missing
namespaceis also a namespace, the default one. It is not “no grouping”. - A wrong namespace is not a no-op. A typo means “this namespace declares nothing that it owned before”, which is a request to delete.
- Several files can share one namespace. They are parsed together and reconciled as one unit, so the plan for a namespace includes every file that declares it.
deploy --namespace <name> selects which namespaces of the parsed files this run processes. You can repeat it. It does not change what a namespace owns.
export --namespace is a different thing. On export, --namespace is the value written into the generated YAML: a label, not a filter. export has no namespace filter. It returns everything that matches the other scopes and writes your namespace onto all of it. If you export the whole workspace under one namespace and deploy that file, one config takes the checks of all other configs.
4. --dry-run and the confirmation prompt are not the same thing
--dry-runcomputes and prints the plan, then exits without a deploy. It never prompts, and it writes nothing.- Without
--dry-run, the same plan is printed. Then the run asks, per namespace, if it applies the plan. Any answer other thanycancels that namespace, and the run continues with the next one. --auto-confirmanswersyfor you. The plan is still printed, but nobody reviews it.
- Without
--auto-confirm, a non-interactive run waits on the prompt. It does not continue and it does not fail. --auto-confirmremoves the review step. It is safe only after you read a--dry-runof the same file.
--dry-run and --auto-confirm together mean --dry-run: nothing is deployed.
5. Authoring the config
A complete, minimal file:id: of your own, as above. It is optional, but it is the address of the check. With an id:, an edit to any other field updates the check and keeps its history. Without one, many edits replace the check (§ 6).
Use version: v1beta2. v1beta1 is legacy and exists only for compatibility. Do not write new files in it.
Check types
Monitor types watch an asset over time and learn what is normal:volume, freshness, field_stats, custom_numeric, category_distribution, table_stats, custom_table_stats. A field_stats column can have its own metric selection, and each metric its own sensitivity.
SQL test types assert that something is true now: not_null, empty, unique, accepted_values, rejected_values, min_max, min_value, max_value, freshness, relative_time, business_rule, business_query, relationships.
business_ruleandbusiness_querycarry your own SQL. They accept{{ table }}, which is replaced with the fully qualified name of the anchored table.- On a
sql_testsdeployment rule,{{ table }}lets one query cover every matched table. Any other{{ name }}token is rejected on save. See SQL tests.
Deployment rules
Deployment rules (deployment_rules, and deployment_exclusions to remove assets again) apply checks by query, not by a list of assets. So a new matching asset is covered without a change to the file.
typetells what a rule deploys.table_statsdeploys monitors.sql_testsdeploys the SQL tests in its owntests:onto every table or view that the selection matches.- On a
sql_testsrule, the schedule, timezone, severity andsave_failuresare set on the rule and apply to every test. A test can override onlyseverity. - A
sql_testsrule deploys each test only onto matched tables that have every column the test reads. The plan lists the others as skipped tests. So a broad selection is safe: each table gets the tests that fit it, and the plan shows where they went. exportwritessql_testsrules back intodeployment_rules, so you can adopt a workspace that has them with the loop in § 1.exportwrites atable_statsrule as the single-assettable_statsmonitors that it covers. Its query form is only for authoring. If a file must keep the query form, keep your hand-written version.
Where to find fields
In order of authority:- The published schema: every field and every accepted value.
examples/: one file per monitor type, per test type and per pattern. You can deploy each file as written.- SQL tests for the test types in prose, and Monitors as code for what the YAML declares and why.
6. What re-deploying changes, and what it replaces
deploy matches a check by its id. So the source of the id decides if an edit is an update or a replacement. A replacement is not an edit: the old check is deleted with its issue history and error runs (and, for a monitor, its learned baseline), and a new check is created.
The id: is the address
With an
id: of your own, everything except the entity and the type is an attribute that you can edit in place. Examples: the metric_aggregation of a custom_numeric, the column of a category_distribution, the columns or SQL of a test, the mode, the segmentation, the partitioning column. Three edits still replace the check:
- A new
idis a new address, so it is a new check. - A move to another entity is a new check, because the API refuses to point a monitor at a different asset.
- A new
typeis a new check. Avolumemonitor that becomes afreshnessmonitor measures something else, so its baseline and history have no meaning.
id:, the content is the identity. This lets two checks of the same type sit on one entity with no ids, but an edit to the content replaces the check. The content is:
- for a
custom_numeric, itsmetric_aggregation; - for a
category_distribution, its column; - for a test, its columns, and for
business_ruleorbusiness_queryalso the SQL; - for every monitor, also the mode, the time-partitioning expression and the segmentation expression.
field_stats monitors on one entity are an exception. Their content does not include columns, so two that differ only there get the same id, and the deploy fails on the duplicate. Give at least one of them an id:.
Monitor edits
Test edits
Deployment rule edits
Rule identity works the same way, and the risk is higher. If you delete asql_tests rule, you delete every test that the rule deployed, with their check entities and history. Nothing can restore them.
- If a rule has no
id:, a rename replaces it. The old rule is deleted with everything it deployed, and the next scheduled sync builds the tests again from the start under the new name. - A monitor rule is safer: its monitors stay while some rule still covers the asset. But the id of the rule changes, so every link and every
id=that someone kept stops working.
This applies to both kinds of rule and to a
deployment_exclusions entry. Everything else is an attribute: the resolver_ql selection and the fields that describe the deployed checks. The two kinds have different attributes. A field of the other kind is rejected, not ignored.
An update in place syncs the rule again. Checks appear on assets that the selection now matches and go away from assets that it no longer matches. Assets that match before and after keep their checks and history.
- An entity-anchored
table_statsmonitor becomes a rule on that one asset, and each asset has one such rule. So the asset is its address, and itsid:accepts only the UUID thatexportwrites for it, which adopts that rule. A name of your own there is rejected. - If you change such a monitor into a query rule under
deployment_rules, it is a different rule with a different selection, not an edit. Expect a create and a delete. - A rule and an exclusion can share a name or an
id:. They are different things, and each keeps its own identity.
Reading a replacement in the plan
--dry-run shows a monitor replacement on one line: Replaced, id changed: <old> → <new>, with what it discards and the fields that differ. It counts as neither a create nor a delete.
- The plan pairs any one monitor that leaves and one that arrives on the same asset with the same type. This can be a renamed
id:, an edit to a monitor without anid:, or two unrelated monitors that share the asset and the type. - Read the diff, not the word “Replaced”. The diff tells which of the three it is. The line above prints the name of the new monitor, so a
name:in the diff means that the old monitor had a different name. - An entity move and a
type:change stay a delete plus a create, in the two sections of the plan. - A replaced test or rule is also a delete plus a create. If you edited an
id:or thename:of a rule, read those two sections.
7. Resets and re-runs: updates that are not free
Some updates in place keep the check but change what it has. The plan shows these. The cost is different for a monitor and for a test.Monitor reset
A monitor reset discards the learned baseline. The monitor has no anomaly baseline until it collects enough history again, and during that time it does not really watch anything. A monitor resets when one of these changes:- its timezone;
- its mode (
anomaly_engine⇄fixed_thresholds); - its schedule (type, time of day, minute of hour, or delay);
- its
metric_aggregation, or the column ortop_k_limitof acategory_distribution; - its time-partitioning expression or interval;
- its segmentation.
metric_aggregation, the category_distribution column, the partitioning expression and the segmentation cause a reset only when the monitor has an id:. Without one, they are part of its identity, so the same edit replaces the monitor, which is worse.
Test re-run
A test re-run discards nothing. A test has no learned state. The plan only tells you that the test runs again: it is re-armed and runs at once, not at its next scheduled time.- A test is re-armed when its recurrence, its severity, its evaluators or its template body change. The template body is any test-specific field, for example the columns, the
valuesof anaccepted_values, or the bounds of amin_max. - A test with no
schedule:runs from a trigger outsidesynqcli. It is not re-armed, and runs at its next external trigger. - A test loses data only on a replacement (§ 6). A new id means a new check entity, so its issues and error runs are gone. An
id:prevents this, not the re-run.
8. Refusals, and what each one means
”managed by other configs”: the deploy is refused
--dry-run. No plan is printed for any namespace until you resolve it.
Try these steps in order:
- Check if the namespace on your side is wrong. The message names the namespace that owns the check. If your file should declare that namespace, fix
namespace, and the conflict goes away. - Stop declaring the check. If it belongs to the other config, remove it from yours. This is usually the correct answer.
- Transfer it, if it must move. Remove it from the owning config and deploy that config first. Then add it to yours and deploy again. Between the two deploys the check does not exist: it is deleted and created again, so it loses its history. There is no handover in place.
”Managed by API (No Config)” and monitors created in the app: not refused
Both show in the plan under their own headings, and neither blocks anything. They are adoptions. If your config declares a check that was created in the web app, or over the API without a namespace, your namespace takes it. After that it is yours, and a later deploy that stops declaring it deletes it. This is usually what you want, but it is silent and one-way. If someone maintains a check in the UI on purpose, a config that declares it takes it from them.Everything else that stops a run
These errors are collected over every file and namespace and reported together. Then the run exits and deploys nothing. Fix them all in one pass.
9. Running it non-interactively
- The
--client-id/--client-secretflags. QUALITY_CLIENT_ID/QUALITY_CLIENT_SECRET, or a pre-issuedQUALITY_TOKEN.- A
.envfile in the working directory. - A cached browser login.
synqcli auth login --coalesce [--organization <org-id>] caches a login with your Coalesce account. That credential carries your Coalesce Quality roles in the workspace of the organization, not consented permissions.
- Select the deployment by name:
--region eu|us|au, or--endpointfor a self-hosted one.synqcli auth loginremembers the choice, andsynqcli auth use <region>changes it, so later commands need no flag. - A wrong region is the most expensive mistake here. It reconciles a workspace that does not declare your checks, and to reconcile means to delete.
- Confirm the target before you write. Every
deployandexportprints the workspace that it connected to before it does anything. Read that line. - Set
--log-fileor$QUALITY_LOG_FILEto get a JSONL execution log: one record per phase, per namespace result and per error. Parse this log, not the formatted output. - Exit code
0means the command did what it said.1means it did not: a failed deploy or export, and also a mistyped flag, an unknown command, a missing argument, or an unusable--region/--endpoint. So a CI step that checks the status also catches an invocation that an oldersynqclidoes not understand. - A cancelled namespace is not a failure: a declined prompt exits
0.--dry-runalso exits0, with or without changes. If a CI gate needs “nothing to deploy”, read the log file, not the status.
10. advisor: propose tests for an asset
advisor asks a model to suggest tests for an entity, and can write them into a config:
--outputis a directory. It gets one file per entity. An existing file is skipped unless you pass--force.--entity-idcan be repeated.--columnsnarrows the scope.--instructions-filereads a long prompt from a file.--severitysets the default severity, and--namespacethe group it writes into.--connectionslets it profile the warehouse. With it, it proposes realaccepted_valuesand real bounds, not guesses.
--output, then read, then deploy --dry-run. It only proposes, and it does not know what the workspace already has.
Specific instructions give better results. Some that work:
11. What each command costs
schema: free and offline. No credentials, no API call.auth status: reads local files only. No network.export: reads the workspace, and is cheap. It refuses to overwrite an existing output file, so give it a new path.deploy --dry-run: resolves every entity id and compares with the workspace. Cheap, and always worth it.deploy: writes. The cost is the blast radius, not the runtime: it creates, updates and deletes in one pass.advisor: one model call per entity, plus warehouse metadata reads, and with--connectionsprofiling queries on the warehouse. It is the only command with a cost per run that matters. Limit it with--entity-idand--columns.upgrade: replaces this binary with the latest release, verified against thechecksums.txtof the release.upgrade --checkreports what it would do and changes nothing. Neither touches the workspace.
synqcli mentions a newer release on stderr. It is silent when an agent drives it, when output is not a terminal, in CI and in a container, so the notice never gets into output that you parse. QUALITY_NO_UPDATE_CHECK=1 turns it off.
12. Never do these
- Never run
deployon a workspace whose config you did not write without first reading a--dry-run. Deploy is a reconcile, so an incomplete file is a delete. - Never guess a
namespace. A typo does not narrow the run, it asks for a delete. - Never export the whole workspace under a namespace and deploy it back.
export --namespacelabels, it does not filter (§ 3). - Never invent a UUID for an
id:. A name of your own is the supported address (§ 6). A UUID that you made up addresses nothing, and on an entity-anchoredtable_statsmonitor it adopts a rule that does not exist. Copy a UUID only fromexportor from the app. - Never assume that a rename is a rename. Check it against § 6, and read the replacement line and the delete list in the plan.
- Never confirm a monitor reset yourself. If the plan says that a monitor resets, say so and let the person decide, because the discarded baseline cannot be recovered. A test that “will run again” discards nothing and needs no pause.
- Never point a config at a workspace that you did not confirm. Run
auth status, and read the workspace line that the command prints. - Never edit the schema or the CLI reference by hand. Both are generated.
13. When something looks wrong
The full command and flag reference is generated from the CLI and published as the CLI reference. For what the YAML declares and why, start at Monitors as code. For the test types in detail, see SQL tests and Deployment rules.