Agent mode
When the customer's database lives behind a firewall, agent mode runs the validation inside their network. Only redacted metadata (counts, success flags, schema column names, severity) crosses back to PLACEHOLDER Cloud — sample failing rows, comparator values, and rendered SQL never leave the customer's side.
Compare with direct mode, where the cloud worker connects straight to the customer DB with an encrypted DSN.
Architecture
- A user clicks Run now (or a schedule fires). The API resolves the
checkpoint → asset → datasource; if the datasource is agent-mode it
writes a
RunQueuerow withexecution_mode = "agent"and does not enqueue a worker job. - The on-prem agent polls
GET /api/agent/v1/jobsevery few seconds. - The agent calls
POST /api/agent/v1/jobs/{id}/claimto grab the row atomically (a Postgres-side compare-and-set onclaimed_by_agent_token_id IS NULL). - The claim response carries the dispatch envelope: the data asset's table + schema + dialect, the rule suite's expectations, the custom-SQL rules. No DSN. The agent has its own local mapping from datasource UUID to local connection string.
- The agent constructs the same Great Expectations pipeline the cloud
worker uses (the runner lives in
shared/, imported by both), runs it against the customer DB, and then runs the result through the redactor (shared/src/dq_shared/agent_payload.py). - The agent
POSTs the redacted payload toPOST /api/agent/v1/jobs/{id}/complete. The API runs the same redactor as a validator — if anything forbidden slipped through, the payload is rejected with400and the job stays claimed for retry. - On accept, the API writes a
ValidationResultrow, marksRunQueue.status = "success"(or"failed"), and fires the existing alert + anomaly evaluation pipeline.
What crosses the boundary
PLACEHOLDER Cloud receives only:
| Field | Why it's safe |
|---|---|
success (overall and per-expectation) | Boolean flag, derived from counts. |
statistics (evaluated_validations, successful_validations, success_percent) | Numeric counts. |
expectation_type | The rule's machine name (expect_column_values_to_not_be_null). |
description | Author-supplied prose from the rule suite — not data-derived. |
severity | Author-supplied (error / warning / info / critical). |
columns | Schema metadata. Column names only — the redactor extracts these from kwargs.column / kwargs.column_list and drops the rest of kwargs. |
result.unexpected_count, result.evaluated_count | Counts. |
result.observed_value | Only kept if it's a number; strings or arrays are dropped. |
PLACEHOLDER Cloud never receives:
unexpected_rows— the sample failing rows GX collects.rendered_sql— the{batch}-substituted query the runner executed (it carries both schema names and value-comparator literals).kwargs— the GX expectation kwargs, which includevalue_set, literalmin_value/max_value, the unexpected-rows query, regex patterns, etc. The redactor extracts column names intocolumnsand drops the rest.partial_unexpected_list/unexpected_list/unexpected_index_list— alternate row-sample representations.
The cut is enforced twice:
- Agent side —
redact_gx_result(payload)walks the full GX result and emits only the allowed-keys subset before the agent submits. - Cloud side —
validate_redacted_payload(payload)walks the incoming payload at/completeand raises400if any forbidden key is present. So even a regressed agent can't smuggle data past the boundary.
Trust model
- The agent authenticates with a
dqa_-prefixed token issued in the cloud UI by an org owner. The token is hashed at rest; only its SHA256 lives inpublic.agent_tokens. - The token scopes the agent to one org + optional workspace. The cloud
rejects job claims whose linked datasource isn't owned by the
authenticating token's
agent_token_id. - The atomic CAS on
/claimmeans two agents (e.g. a blue/green rollover) can't both run the same job. Exactly one wins; the other gets409. - Credentials to the customer DB live only in the agent's local env
(
DQ_AGENT_DATASOURCE_DSNS). They never enter the cloud control plane and are never carried in the dispatch envelope. The agent'ssys.excepthookscrubs the DSN from any uncaught traceback before it lands in stderr, so even a misconfigured DSN can't leak via the/failerror message.
Connection status on the datasources list
Because an agent-mode datasource can only run a profile or checkpoint when its agent is actually connected, the agent's health is surfaced inline on the Data Sources list (and on each datasource's detail page), not just under Settings → Agents:
| Badge | Meaning |
|---|---|
| Agent online (green) | The agent checked in within the last 5 minutes. |
| Agent offline (red) | The agent has connected before but not within the last 5 minutes. |
| Agent pending | The agent token is registered but has never checked in. |
| Agent revoked (red) | The agent's token was revoked — it can no longer connect. |
| Direct (neutral) | A direct-mode source; no agent is involved. |
The "online" window is the same 5-minute last-seen threshold the worker
uses to reclaim a stale claim (agent_claim_ttl_seconds, default 300 s),
so the badge, the Settings → Agents page, and stale-claim reclamation all
agree on what "online" means.
When the agent is offline or revoked, the per-asset Profile button on the datasource detail page is disabled with the tooltip "Agent is offline. Check Settings → Agents." — this stops a user from triggering a run that is guaranteed to fail (or get stuck "running") while the agent is down.
The status rides on the datasource API payload itself (an agent block of
{status, last_seen_at, token_name} on the V2 datasources list/get), so
every role that can see the datasource sees the badge — it does not require
the admin-only agent-tokens endpoint. The block carries only non-secret
status fields; the token hash never leaves the server.
Choosing between modes
| Use direct mode when… | Use agent mode when… |
|---|---|
| The cloud can reach the customer DB. | The DB is in a private VPC / on-prem network. |
| The customer is willing to share encrypted credentials with the cloud. | The customer can't share credentials. |
| You want minimum operational surface for the customer. | The customer already runs services in their network. |
| You want zero extra latency between trigger and run. | You're OK with the agent's poll interval (default 5 s). |
The two modes coexist per-org and per-datasource — one customer can mix direct-mode (their public Redshift cluster) and agent-mode (their internal HR Postgres) under the same workspace.
See also
- Deploy the agent — operator how-to.
- Multi-tenancy — schema isolation between orgs.
- Datasources — datasource registration + the
execution_modecolumn.