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: the loop to follow, how
to read what it prints, the refusals and what each one actually means, and the
mistakes that cost real time. It is written to be acted on top-down.
synqcli deploy reconciles a workspace with a YAML file. You declare which
monitors and tests belong on which assets; deploy makes the workspace match.
That means it creates, it updates, and it deletes — anything the file’s
namespace owns that the file no longer declares is removed. Understanding that one
sentence prevents most of the damage this tool can do.
Two more facts decide almost everything else, and both are covered in full below.
A check’s identity is derived from what it is and what it watches, so some edits
update a check in place and others silently replace it. And a check belongs to
exactly one namespace, so pointing a second config at it is refused rather than
merged.
1. The loop
deploy gets changes they did not write.
Get the schema into the editor first. It is the field-level reference, and it
is authoritative in a way prose is not:
2. Entity ids: resolve them, never guess them
This is the most common failure, and the one the CLI can help with least. A wrong id is not a typo it can correct — the asset simply is not there. An entity id in the YAML is written in one of three forms, and all three are resolved against the workspace before anything is deployed:
The first two are the same thing written two ways. The third is different: it is
what the table is called in the warehouse, and it is matched only for ids the
first lookup did not find.
Resolution can only succeed for an asset the platform already knows about, and
only for a table, a view or an equivalent model — a dashboard or a job is not a
monitorable entity, so an id that names one fails the same way a nonexistent one
does.
Get real ids rather than constructing them. Two reliable sources:
synqcli export --source all out.yamland read theentities[].idvalues it writes. They are already in the simplified form the config accepts.- The asset’s page in the web app, which 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 expecting to see a plan at all.
3. The namespace is the reconcile boundary
namespace groups checks so that different teams reconcile independently. It is
the single most consequential field in the file:
- Deleting is scoped to it.
deployremoves checks that this namespace owns and the file no longer declares. It never touches another namespace’s checks. - A missing
namespaceis itself a namespace — the default one — not an absence of grouping. - Getting it wrong is not a no-op. A typo means “this namespace declares nothing that it used to own”, which is a request to delete, not a request to skip.
- Several files may share one namespace. They are parsed together and reconciled as one unit, so a plan for a namespace reflects every file that declares it.
deploy --namespace <name> filters which namespaces in the parsed files are acted
on; it is repeatable. It does not change what a namespace owns, only which of them
this run processes.
export --namespace is a different thing entirely. On export, --namespace
is the value written into the generated YAML — a label, not a filter. export
has no namespace filter, so it returns everything matching the other scopes and
stamps whichever namespace you named onto all of it. Exporting the whole workspace
under a namespace and deploying that file back is how one config comes to claim
checks that belonged to five others.
4. --dry-run and the confirmation prompt are not the same thing
They read as one thing and are not:
--dry-runcomputes and prints the plan, then exits without deploying. It never prompts. Nothing is written.- Without
--dry-run, the same plan is printed and then the run asks, per namespace, whether to apply it. Answering anything other thanycancels that namespace and moves to the next. --auto-confirmanswersyfor you. The plan is still printed; nothing is reviewing it.
--auto-confirm a non-interactive
run blocks on the prompt — it does not proceed, and it does not fail. And
--auto-confirm does not skip the review step so much as remove it, which is only
safe when a --dry-run of the same file was read first.
The order that is both non-blocking and reviewed:
--dry-run and --auto-confirm together are accepted and mean --dry-run:
nothing is deployed.
5. Authoring the config
A complete, minimal file:version: v1beta2 is the current format; v1beta1 is legacy and exists for
compatibility only. Do not author new files in it.
Monitor types watch an asset over time and learn what normal looks like:
volume, freshness, field_stats, custom_numeric, category_distribution,
table_stats, custom_table_stats.
SQL test types assert something is true right now: not_null, empty,
unique, accepted_values, rejected_values, min_max, min_value,
max_value, freshness, relative_time, business_rule, business_query,
relationships.
Deployment rules (deployment_rules, and deployment_exclusions to carve
assets back out) apply monitors by query rather than by listing assets, so a new
matching asset is covered without editing the file.
Where to look for the fields of any one of them, in order of authority:
- the published schema — every field, every accepted value.
examples/— one file per monitor type, per test type and per pattern, all of them deployable 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
A check’s id is derived from its identity, deterministically, so that runningdeploy twice updates rather than duplicates. The consequence is the part that
surprises people: change something in the identity and you have not edited a
check, you have replaced it. The old one is deleted, with its history and its
learned baseline, and a new one is created.
A monitor’s identity is: its id field, the namespace, the entity it watches, the
monitor type, the mode (anomaly_engine vs fixed_thresholds), the
time-partitioning expression, the segmentation expression, and — for
custom_numeric — its metric_aggregation, or for category_distribution its
column.
A SQL test’s identity is: its id field, the namespace, the entity, the test type,
the columns it names, and — for business_rule and business_query — the SQL
itself.
So, for monitors:
And for tests:
--dry-run shows a replacement as a delete plus a create, in the two separate
sections of the plan. It is not labelled “rename”, and nothing warns you, so the
delete list is what you read to catch one.
If you want a rename to be cosmetic, it has to be a field that is not in the
identity list above. Otherwise accept that history restarts, or leave the id alone.
“Update in place” above means the check keeps its id and its history. A few of
those updates keep the check but still discard what it has learned, which is the
next section.
7. Resets: an update that still discards history
Some in-place updates keep the check but throw away what it has learned. The plan flags these, and they are worth pausing on: a monitor that resets has no anomaly baseline until it has collected enough history again, and during that window it is not really watching anything. A monitor resets when its timezone changes, its schedule changes (type, or the time of day / minute of hour, or the delay), its time-partitioning interval changes, or acategory_distribution’s top_k_limit changes.
A test resets when its recurrence, its severity, its evaluators, or its
template body change — the last one meaning any test-specific field that is not
one of the columns, such as the values of an accepted_values test or the
bounds of a min_max.
Note the asymmetry, because it is not intuitive: changing a test’s severity
resets it; changing a monitor’s severity does not.
There is no flag to suppress a reset and no way to deploy the change without it.
The choice is to accept it or to leave the field alone, so the useful thing an
agent can do is notice it in the plan and say so before confirming, rather than
discover it afterwards.
8. Refusals, and what each one means
”managed by other configs” — the deploy is refused
--dry-run
too — so no plan is printed for any namespace until it is resolved.
The recovery, in the order to try it:
- Confirm it is not just the wrong namespace on your side. The message names
the namespace that owns the check. If that is the namespace your file should
have declared, fix
namespaceand the conflict disappears. - Stop declaring it. If it genuinely belongs to the other config, remove it from yours. This is the right answer far more often than transferring it.
- Transfer it deliberately, if it really should 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 recreated, so it loses its history. There is no in-place handover.
”Managed by API (No Config)” and monitors created in the app — not refused
Both appear in the plan under their own headings, and neither blocks anything. They are adoptions: a check created in the web app, or created over the API without a namespace, is taken over by your namespace when your config declares it. From then on it is yours, and a later deploy that stops declaring it will delete it. That is usually what was wanted, but it is silent and it is one-way. If a check was deliberately maintained in the UI, declaring it in a config takes it away from whoever maintains it there.Everything else that stops a run
All of these are collected across every file and namespace and reported together,
then the run exits without deploying anything. That is deliberate: fix them in one
pass rather than one at a time.
9. Running it non-interactively
- Credentials, in precedence order:
--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. Client credentials beat a cached login, so CI is unaffected by whoever logged in last. - The deployment is named, not spelled out:
--region eu|us|au, or--endpointfor a self-hosted one.synqcli auth loginremembers the choice andsynqcli auth use <region>switches it, so later commands need no flag. A wrong region is the most expensive mistake available here — it reconciles a workspace that does not have your checks declared, and reconciling means deleting. - Confirm the target before writing. Every
deployandexportprints the workspace it connected to, before it does anything. Read that line. - An execution log in JSONL is written when
--log-fileor$QUALITY_LOG_FILEis set: one record per phase, per namespace result and per error. This is the machine-readable version of the run, and it is much better to parse than the formatted output. - Exit codes:
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 an oldersynqclidoes not understand, rather than passing having deployed nothing. - A cancelled namespace is not a failure. Declining the prompt exits
0.--dry-runalso exits0whether or not there were changes. If a CI gate needs “nothing to deploy”, read the log file rather than the status.
10. advisor — propose tests for an asset
advisor asks a model to suggest tests for an entity and can write them straight
into a config:
--output is a directory; one file is written per entity, and an existing file
is skipped unless --force is passed. --entity-id is repeatable. Other flags
worth knowing: --columns to narrow the scope, --instructions-file for a long
prompt, --severity for the default it assigns, --namespace for the group it
writes into, and --connections to let it profile the warehouse — which is what
lets it propose real accepted_values and real bounds instead of guesses.
Review what it writes before deploying it. --output, then read, then deploy --dry-run is the safe order. It proposes; it does not know what the workspace
already has.
Being specific in the instructions pays. 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. Cheap. Note that it refuses to overwrite an existing output file, so give it a fresh path.deploy --dry-run— resolves every entity id and diffs against 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— a model call per entity, plus warehouse metadata reads and, with--connections, profiling queries against the warehouse itself. The only command here with a per-run cost worth thinking about; scope it with--entity-idand--columnsrather than pointing it at everything.upgrade— replaces this binary with the latest release, verified against the release’schecksums.txt;upgrade --checkreports what it would do and changes nothing. Neither touches the workspace.synqclialso mentions a newer release on stderr about once a day, and stays silent when an agent is driving it, when output is not a terminal, in CI, and in a container — so it never lands in output you are parsing.QUALITY_NO_UPDATE_CHECK=1turns it off outright.
12. Never do these
- Never run
deploywithout reading a--dry-runfirst on a workspace whose config you did not author. Deploy is a reconcile, so an incomplete file is a delete. - Never guess a
namespace. A typo does not scope the run down, it asks for a deletion. - Never export the whole workspace under a namespace and deploy it back.
export --namespacelabels, it does not filter — see § 3. - Never hand-assign check ids. They are derived, and that derivation is what makes a re-deploy an update instead of a duplicate.
- Never assume a rename is a rename. Check it against § 6 first, and look for a matching delete and create in the plan.
- Never self-confirm a reset. If the plan says a check resets, say so and let the person decide — the history it discards is not recoverable.
- Never point a config at a workspace you have not confirmed.
auth status, and read the workspace line 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 itself and published
as the CLI reference. For what the YAML
declares and why, start at
Monitors as code; for the test
types in detail, SQL tests and
Deployment rules.