Tracking Agent docs
Tracking Agent reference: config, registry and commands
This reference lists every key in .datalyze/config.yaml and _conventions.yaml, every registry entry field, and every CLI and comment command. Each key matches what the runner reads, and a key you leave out takes the default shown. _conventions.yaml is merged over the Datalyze defaults, so it only needs what differs.
What goes in .datalyze/config.yaml?
.datalyze/config.yaml says how the check runs in one repo. Setup writes it; it's yours to edit in a pull request. The runner reads it from the pull request's base branch (or the default branch when the base has none), never from the pull request being checked, so a change takes effect once it merges. The same goes for everything else a pull request is judged by: _conventions.yaml and _baseline.json in the registry, .datalyze/decisions.yaml, .datalyze/conventions.md and a committed data snapshot. From the pull request itself the check takes only what waives no finding: where the typed tracker lives (typed_tracker), which side code runs on (scan.server_paths, scan.client_paths) and names built at runtime (scan.dynamic_names, each still checked against the plan).
| Key | Default | What it does |
|---|---|---|
model.provider | anthropic | anthropic, openai, google, bedrock, vertex, gateway or claude-code. See models |
model.models.small / main / verifier | Built in for Anthropic, Bedrock and Vertex | The model id for each role |
model.api_key_env | The provider's usual variable | The environment variable holding the model key |
model.base_url, model.gateway_api | None | Gateway only: its URL, and whether it speaks the anthropic or openai API |
model.region, model.project | From the cloud's environment | Bedrock or Vertex region; Vertex project |
model.pricing | Built in for Anthropic's API only | US dollars per million tokens per model: input, output, optional cacheRead, cacheWrite |
model.max_cost_usd | 10 | Stops a run once its model spend passes this |
install_audit.max_cost_usd | 25 | The same cap for the install audit, which reads the whole codebase |
reviewers | None: setup asks | GitHub logins or @org/team who may approve, reject and sign off (org/team without the @ is read as a team too) |
maintainers | The reviewers | Who may /datalyze skip |
allow_author_approval | true | Whether a pull request's author may decide on their own tracking: approve, reject, drop, answer, sign off or skip |
check_drafts | false | Check draft pull requests too |
registry | analytics/registry | Where the registry lives |
checks | None | Your own checks for verification, each with name, run and optional timeout_minutes |
tests.enabled | true | Generated tests during verification |
tests.native.language, dir, run, timeout_minutes | None | A mobile app's generated tests: kotlin, swift or dart, the unit-test folder (tests go in datalyzetests inside it), the command for one test class ({class}, {file}, {name}), and a time limit (default 15). See mobile apps |
install | None; setup picks one from your lockfile | Installs the pull request's own dependencies before verification, such as npm ci |
formatter | None | Runs on the files the agent changed, such as npx prettier --write, without a shell |
money_patterns | payment, order, purchase, checkout, subscription, invoice, refund, revenue, billing, plan_ | Event names or sections that always need their own approval |
snapshot | None | A data snapshot file the agents and verifier read |
analytics.tool, project_id, region, host, warehouse, source, location, adapter | None | Where analytics data comes from. tool (mixpanel, posthog, amplitude or warehouse) picks the snapshot recipe; for warehouse, warehouse is bigquery, snowflake or clickhouse, source says how the events are laid out, and location is BigQuery's job location. adapter names the SDK the app sends with when the data comes from elsewhere. See connecting analytics |
sheet.mode, spreadsheet_id, url_env, token_env | None | apps_script or service_account, and where to find the Sheet's receiver |
requests.max_per_run | 5 | How many new rows on the Sheet's Requests tab one run turns into pull requests |
platforms | None | Where the product ships: web, server, ios, android, flutter, react_native |
live_test.enabled | Off | The live test, verification layer 5d. See verification |
live_test.mode | dev_server when start is set, else preview | dev_server or preview |
live_test.start.command, ready_path, timeout_seconds, unset_env | None, /, 240, none | Dev server: the command ({port} is filled in), the path that answers once it's up, how long to wait, and env vars removed so a server-side SDK can't send real events |
live_test.preview_url | The deployment GitHub records for the commit | Preview: a fixed URL, or a template with {pr}, {sha} and {branch} |
live_test.wait_minutes | 10 | Preview: how long to wait for the commit's deployment |
live_test.headers | None | Headers sent only to the app's own origin; a $NAME value reads an env var, such as a preview's bypass secret |
live_test.account.username_env, password_env | None | Env vars holding a test account. Flows type {{username}} and {{password}}; the model never sees the values |
live_test.capture_paths | None | First-party proxy paths that are analytics endpoints, such as { /mp/api: mixpanel } |
live_test.block_hosts | None; ad and tracker hosts are blocked by default | Extra hosts to block during the test |
live_test.flush_ms | 6000 | How long to wait after each flow for batched SDK sends |
live_test.max_flows | 8 | The most flows one run plans |
watch.min_events, hours, lookback_days | 50, 48, 14 | Post-release watch: events of each kind to wait for, the hours after a merge when it runs anyway, and how many days back to look for merged pull requests |
watch.open_fix_prs | true | Whether the watch opens fix pull requests; false makes it only report |
watch.app_version, release, release_wait_days | Found from the usual files for a mobile tracker; from tags; 45 | A mobile app's version file ({ file, pattern }, or false to watch by time), which release ships a merged change (at_merge or after_merge), and the days to wait for it to appear |
review_url | https://www.joindatalyze.com/tracking-agent/review | The review page the summary comment links to |
licence.key_env | DATALYZE_KEY | The environment variable holding the workspace key |
licence.pin | The newest release | A method version to stay on, such as 2026.10.3 |
# .datalyze/config.yaml
model:
provider: anthropic
max_cost_usd: 10
reviewers: [ana-k, '@acme/analytics']
allow_author_approval: false
registry: analytics/registry
install: npm ci
checks:
- { name: typecheck, run: npx tsc --noEmit }
- { name: lint, run: npm run lint }
platforms: [web, server]
analytics:
tool: mixpanel
project_id: '1234567'
region: us
sheet:
mode: apps_scriptWhat goes in _conventions.yaml?
| Key | Default | What it does |
|---|---|---|
naming.event_pattern | snake_case with at least two words | The regex every event name must match |
naming.property_pattern | snake_case | The regex every property name must match |
naming.namespaces, namespace_separator | None | Allowed first segments, for plans that prefix names with an area |
naming.past_tense | off | off, warn or error: whether the last word must be past tense |
track_via | [client, server] | Where an event or user property can be sent from |
data_types | string, numeric, boolean, array, datetime, object | Allowed property types |
rule4_allowlist | [error_occurred] | Events allowed to be separate failure events |
pii_patterns, pii_exceptions | email, phone, name, query, prompt, text, password, token and similar | Property names treated as personal data, and reviewed exceptions |
budget.new_events_per_pr | 5 | More new events than this in one pull request needs a written reason each |
set_once_patterns | first_, signup_, signed_up_, initial_, acquisition_, _at_signup | User property names that should use set_once |
typed_tracker.language, out_dir, import_path, package, server_via, targets | typescript, analytics/generated, @/analytics/generated, none, [server], none | The tracker's language (typescript, swift, kotlin or dart), where it goes, how code imports it, the Kotlin package, which track_via values go on serverTrack, and more apps in the repo with their own tracker |
scan.extensions, exclude | JS, TS, Swift, Kotlin and Dart extensions; node_modules, build folders, Pods, .gradle, .dart_tool | What the scanner reads |
scan.adapter_paths | analytics/adapter, analytics/generated | The only files allowed to call the analytics SDK |
scan.direct_sdk_patterns, direct_sdk_exceptions | Mixpanel, PostHog, Amplitude, Segment and gtag calls | Direct SDK calls the scanner fails on, and reviewed exceptions with a reason |
scan.legacy_trackers | None; the install audit fills it | Tracking calls written before the typed tracker, by callee (with from in JS and TS, * for one name in Swift, Kotlin and Dart, and name_label / props_label for labelled arguments) or by pattern |
scan.dynamic_names | None; the install audit fills it | Per file, the names a runtime-built call can send (such as a map of steps to names), when each appears as a string in the code, so those events reach the registry |
scan.server_paths, client_paths | server/, api/, functions/ and similar | Path prefixes whose code runs on the server or the client |
audit.ignore_events, vendor_properties | Names starting $, mp_, [Auto], [Amplitude] | Events and properties the analytics tool adds itself |
audit.min_fill_rate, max_duplicate_rate, min_identified_share | 0.95, 0.05, 0.2 | Audit thresholds |
reviewers | None | Written by setup with the same list as config.yaml |
What fields does a registry event entry have?
| Field | Required | What it holds |
|---|---|---|
name | Yes | The event name, matching naming.event_pattern |
description | Yes | When it fires, precisely, with its boundaries |
question | On new events | The question it answers |
track_via | Yes | Where it's sent from, one of track_via |
status | Yes | planned, implemented, needs_review or deprecated |
section | No | Grouping in the Sheet |
pair | No | The intent event this outcome closes |
properties | No | Each with name, type, example (real values; enums as a | b) and required (default true) |
code_refs | Written by the scanner | Where the call is, refreshed on the main branch |
changelog | Written by the PR check | Each change with date, ref and change |
deprecated_reason, replaced_by, dual_fire_until | On deprecated events | Why, what replaces it, and the last day of the dual-fire window (a date runs to the end of that day, UTC) |
external | No | true when another service sends it, so no call in this repo isn't "dead" |
# analytics/registry/posts.yaml
events:
- name: post_created
section: Posts
description: >-
A post is saved by the backend after the user clicks Publish. Fires once on the server
when the row is created, not when the editor closes.
question: How many posts do users publish, from which entry point, and how many fail?
track_via: server
status: implemented
pair: publish_clicked
properties:
- { name: post_id, type: string, example: 5b48c62e-5484-4225-95d5-570d4cb45992 }
- { name: entry_point, type: string, example: editor | quick_post }
- { name: status, type: string, example: success | failed }
- { name: failure_reason, type: string, example: null | validation | server_error }What fields does a user property entry have?
name, type, example, operation (set, set_once, increment, append or unset), set_via, set_when and status are required. source, question, description, pii, external, code_refs, changelog, deprecated_reason and replaced_by are optional. Super properties take name, type, example and set_when; group types in _groups.yaml take a key and a list of properties shaped like user properties without operation.
Which Tracking Agent CLI commands are there?
| Command | What it does |
|---|---|
datalyze init | Sets the Tracking Agent up on a repo: the browser setup's steps in the terminal. It has a flag for every setup answer (datalyze init --help), and --dry-run prints the files without touching GitHub |
datalyze install-audit | Runs the install audit on a checkout: registry, baseline, typed tracker and report |
datalyze init-registry | Creates an empty registry with the Datalyze default conventions |
datalyze validate | Checks the registry against the hard rules |
datalyze scan | Compares tracking calls in the code with the registry |
datalyze generate | Writes the typed tracker from the registry |
datalyze adapter <vendor> | Writes the adapter for each typed tracker: mixpanel, posthog, amplitude, segment, rudderstack or ga4 |
datalyze snapshot mixpanel|posthog|amplitude|warehouse | Builds the data snapshot from Mixpanel, PostHog, Amplitude, or SQL on BigQuery, Snowflake or ClickHouse |
datalyze audit | Compares the plan, the code and what the analytics tool received |
datalyze draft | Adds a draft entry for every tracking call the registry doesn't know |
datalyze diff | What a branch changed about tracking, against --base |
datalyze check | Runs the PR check: --pr <n> on GitHub, or the current branch against --base locally |
datalyze rules | The scripts-only check, datalyze/rules |
datalyze action | The GitHub Action's entry point (--job tracking, rules, sync-sheet, requests, watch or after-merge) |
datalyze sync-sheet | Rebuilds the Google Sheet from the registry, keeping the human columns |
datalyze after-merge | On the default branch: moves what shipped from planned to implemented and marks signed-off proposals, as one pull request from datalyze/after-merge |
datalyze requests | Turns new rows on the Sheet's Requests tab into proposal pull requests, and refreshes the Status of rows that have one |
datalyze watch | The post-release watch: a PR-scoped audit of recently merged pull requests once traffic arrives, or after 48 hours. --local runs it on a laptop for pull requests checked locally |
datalyze fix | Opens fix pull requests, one per area, for chosen audit findings, through the PR check |
datalyze import-sheet | Writes a registry from an existing tracking-plan Sheet: --csv exports of its tabs, or --tabs read from the connected Sheet |
datalyze github-app create | Creates your own GitHub App so the agent's commits run your CI |
datalyze workflow | Writes .github/workflows/datalyze.yml for an action reference |
datalyze method bundle | Builds the method bundle from a method folder, and signs it with --sign-key |
datalyze method release | Snapshots the method folder as a new version, if it changed |
datalyze baseline | Accepts today's findings as legacy debt (analytics owner only) |
datalyze vocabulary | Rebuilds _properties.yaml from the events, keeping synonyms |
Every command except init takes --root <repo> and --registry <dir>. validate, scan, audit and diff also take --md <file> and --json <file> to save their report, and sync-sheet takes --json <file>. Exit codes: 0 clean, 1 problems found, 2 a usage or registry error.
Which options do the CLI commands take?
| Option | Command | What it does |
|---|---|---|
--since-baseline | validate | Only findings added since _baseline.json |
--changed-files <f> | scan | Limit drift to what a change touched (git diff --name-only output) |
--fail-on-drift | scan | Exit 1 when code and registry disagree |
--update-status | scan | On the main branch only: planned to implemented, and refresh code_refs |
--check | generate | Exit 1 if the typed tracker is out of date |
--base <ref> | diff, check | The ref to compare against |
--snapshot <file> | audit, draft, install-audit, watch | The data snapshot |
--events, --area, --since, --pr | audit | The audit's scope |
--apply | audit | Write the statuses the data proves, and _audit.json for the Sheet |
--project-id, --region, --host, --days, --cache, --out | snapshot | The project; its region (us, eu or in for Mixpanel, us or eu for Amplitude); the PostHog host (default https://us.posthog.com); the window (default 30 days); a 6-hour answer cache; and the output file |
--warehouse, --layout, --schema | snapshot warehouse | Override analytics.warehouse and analytics.source from .datalyze/config.yaml |
--pr <n>, --repo <owner/name> | check, rules; --repo also github-app create, requests, watch and fix | The GitHub pull request and repo |
--comment <text>, --as <login> | check, locally | Add a reviewer's comment first, such as /datalyze approve |
--method-dir <dir> | check, rules, install-audit, requests | Read the method from a folder instead of the licensed bundle (development) |
--job <job> | action | tracking, rules, sync-sheet, requests, watch or after-merge |
--items <names> | fix | The audit items to fix, comma-separated |
--audit <file> | fix | The audit file to read buckets and checks from (default the registry's _audit.json) |
--source <text> | fix | Where the findings came from, such as "Watch PR #12" |
--dry-run | sync-sheet, after-merge | Show what would change without writing to the Sheet or opening a pull request |
--pending-dir <dir> | sync-sheet | Fill the Pending Changes tab from the proposal files in this folder |
--csv <files>, --tabs <names> | import-sheet | CSV exports of the old Sheet's tabs, comma-separated, or tab names to read from the connected Sheet |
--org <org>, --name <name> | github-app create | Create the App under an organisation, with this name |
--version <v>, --dir <dir>, --sign-key <file>, --out <file> | method bundle | The bundle's version, the method folder, the signing key and the output file; method release takes --dir too |
--action <ref> | workflow | ./path inside the repo or owner/repo/path@ref |
| Its own flags | init | One per setup answer, plus --yes and --dry-run; run datalyze init --help |
--local, --now <time> | watch | Watch the pull requests checked locally (merge commits naming #n); run as of a given time |
Which pull request comment commands are there?
| Command | What it does |
|---|---|
/datalyze approve | Approves every pending additive, high-confidence item |
/datalyze approve p1,p3 | Approves these items. The review page adds @<sha>, as in approve p1,p3 @1a2b3c4, so the approval covers exactly the proposal reviewed; a typed command doesn't need it |
/datalyze reject p2 "reason" | Rejects an item, with the reason |
/datalyze drop p2 | Drops an item after it failed verification |
/datalyze choose p1 B | Answers a "Needs a decision" question |
/datalyze answer q1 "text" | Answers an open question: a q id (the proposal), a d id (a decision raised while implementing) or a v id (the verifier) |
/datalyze retry | Runs analysis or verification again, including a run stuck on analysing or implementing |
/datalyze signoff | Signs off on the verified result |
/datalyze skip "reason" | Maintainers only: passes the check without running it |
Reviewing says who can use each one; the lists come from .datalyze/config.yaml on the pull request's base branch. Commands run from the workflow on your default branch, which GitHub uses for issue_comment events.
What does a proposal file look like?
# .datalyze/proposals/pr-142.yaml
pr: 142
round: 1
status: in_review # in_review, approved, implemented, verified, signed_off
method_version: 2026.10.3
items:
- id: p1
kind: event # event, event_property, user_property, super_property, identity
action: extend # add, extend, modify, deprecate
breaking: false
target: post_created
summary: The new quick-post button needs its own entry_point value.
question: How many posts come from quick post?
proposed: { ... } # what the agent suggested
final: { ... } # what the reviewer decided; starts as a copy of proposed
evidence:
- code: apps/web/src/posts/QuickPost.tsx:41
confidence: high
note: null # the reviewer's instruction; binding
make_rule: false # true adds the note to .datalyze/conventions.md
checked_unaffected:
- publish_clicked
open_questions: []The file carries content, never decisions: the runner ignores any decision field and takes decisions only from comment commands.
Questions about the reference
How do I see which method version a run used?
It's method_version in the pull request's proposal file. Pin a version with licence.pin in .datalyze/config.yaml.
Can the registry live somewhere other than analytics/registry?
Yes. Set registry in .datalyze/config.yaml, and pass --registry <dir> when you run the CLI yourself.
Which files should only the analytics owner change?
_conventions.yaml, _properties.yaml and _baseline.json, because they change the rules for everyone. Route them to that person in CODEOWNERS, and give .github/workflows/ and .datalyze/ a code owner too, for the reasons on the PR check page.
Is there a simpler way to start a tracking plan than writing YAML?
Yes: the install audit drafts the registry from your code. To design one from scratch, read creating a tracking plan, or have our analytics setup service build it with you.