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.

By Ansh AgrawalUpdated

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).

KeyDefaultWhat it does
model.provideranthropicanthropic, openai, google, bedrock, vertex, gateway or claude-code. See models
model.models.small / main / verifierBuilt in for Anthropic, Bedrock and VertexThe model id for each role
model.api_key_envThe provider's usual variableThe environment variable holding the model key
model.base_url, model.gateway_apiNoneGateway only: its URL, and whether it speaks the anthropic or openai API
model.region, model.projectFrom the cloud's environmentBedrock or Vertex region; Vertex project
model.pricingBuilt in for Anthropic's API onlyUS dollars per million tokens per model: input, output, optional cacheRead, cacheWrite
model.max_cost_usd10Stops a run once its model spend passes this
install_audit.max_cost_usd25The same cap for the install audit, which reads the whole codebase
reviewersNone: setup asksGitHub logins or @org/team who may approve, reject and sign off (org/team without the @ is read as a team too)
maintainersThe reviewersWho may /datalyze skip
allow_author_approvaltrueWhether a pull request's author may decide on their own tracking: approve, reject, drop, answer, sign off or skip
check_draftsfalseCheck draft pull requests too
registryanalytics/registryWhere the registry lives
checksNoneYour own checks for verification, each with name, run and optional timeout_minutes
tests.enabledtrueGenerated tests during verification
tests.native.language, dir, run, timeout_minutesNoneA 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
installNone; setup picks one from your lockfileInstalls the pull request's own dependencies before verification, such as npm ci
formatterNoneRuns on the files the agent changed, such as npx prettier --write, without a shell
money_patternspayment, order, purchase, checkout, subscription, invoice, refund, revenue, billing, plan_Event names or sections that always need their own approval
snapshotNoneA data snapshot file the agents and verifier read
analytics.tool, project_id, region, host, warehouse, source, location, adapterNoneWhere 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_envNoneapps_script or service_account, and where to find the Sheet's receiver
requests.max_per_run5How many new rows on the Sheet's Requests tab one run turns into pull requests
platformsNoneWhere the product ships: web, server, ios, android, flutter, react_native
live_test.enabledOffThe live test, verification layer 5d. See verification
live_test.modedev_server when start is set, else previewdev_server or preview
live_test.start.command, ready_path, timeout_seconds, unset_envNone, /, 240, noneDev 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_urlThe deployment GitHub records for the commitPreview: a fixed URL, or a template with {pr}, {sha} and {branch}
live_test.wait_minutes10Preview: how long to wait for the commit's deployment
live_test.headersNoneHeaders 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_envNoneEnv vars holding a test account. Flows type {{username}} and {{password}}; the model never sees the values
live_test.capture_pathsNoneFirst-party proxy paths that are analytics endpoints, such as { /mp/api: mixpanel }
live_test.block_hostsNone; ad and tracker hosts are blocked by defaultExtra hosts to block during the test
live_test.flush_ms6000How long to wait after each flow for batched SDK sends
live_test.max_flows8The most flows one run plans
watch.min_events, hours, lookback_days50, 48, 14Post-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_prstrueWhether the watch opens fix pull requests; false makes it only report
watch.app_version, release, release_wait_daysFound from the usual files for a mobile tracker; from tags; 45A 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_urlhttps://www.joindatalyze.com/tracking-agent/reviewThe review page the summary comment links to
licence.key_envDATALYZE_KEYThe environment variable holding the workspace key
licence.pinThe newest releaseA method version to stay on, such as 2026.10.3
yaml
# .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_script

What goes in _conventions.yaml?

KeyDefaultWhat it does
naming.event_patternsnake_case with at least two wordsThe regex every event name must match
naming.property_patternsnake_caseThe regex every property name must match
naming.namespaces, namespace_separatorNoneAllowed first segments, for plans that prefix names with an area
naming.past_tenseoffoff, 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_typesstring, numeric, boolean, array, datetime, objectAllowed property types
rule4_allowlist[error_occurred]Events allowed to be separate failure events
pii_patterns, pii_exceptionsemail, phone, name, query, prompt, text, password, token and similarProperty names treated as personal data, and reviewed exceptions
budget.new_events_per_pr5More new events than this in one pull request needs a written reason each
set_once_patternsfirst_, signup_, signed_up_, initial_, acquisition_, _at_signupUser property names that should use set_once
typed_tracker.language, out_dir, import_path, package, server_via, targetstypescript, analytics/generated, @/analytics/generated, none, [server], noneThe 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, excludeJS, TS, Swift, Kotlin and Dart extensions; node_modules, build folders, Pods, .gradle, .dart_toolWhat the scanner reads
scan.adapter_pathsanalytics/adapter, analytics/generatedThe only files allowed to call the analytics SDK
scan.direct_sdk_patterns, direct_sdk_exceptionsMixpanel, PostHog, Amplitude, Segment and gtag callsDirect SDK calls the scanner fails on, and reviewed exceptions with a reason
scan.legacy_trackersNone; the install audit fills itTracking 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_namesNone; the install audit fills itPer 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_pathsserver/, api/, functions/ and similarPath prefixes whose code runs on the server or the client
audit.ignore_events, vendor_propertiesNames starting $, mp_, [Auto], [Amplitude]Events and properties the analytics tool adds itself
audit.min_fill_rate, max_duplicate_rate, min_identified_share0.95, 0.05, 0.2Audit thresholds
reviewersNoneWritten by setup with the same list as config.yaml

What fields does a registry event entry have?

FieldRequiredWhat it holds
nameYesThe event name, matching naming.event_pattern
descriptionYesWhen it fires, precisely, with its boundaries
questionOn new eventsThe question it answers
track_viaYesWhere it's sent from, one of track_via
statusYesplanned, implemented, needs_review or deprecated
sectionNoGrouping in the Sheet
pairNoThe intent event this outcome closes
propertiesNoEach with name, type, example (real values; enums as a | b) and required (default true)
code_refsWritten by the scannerWhere the call is, refreshed on the main branch
changelogWritten by the PR checkEach change with date, ref and change
deprecated_reason, replaced_by, dual_fire_untilOn deprecated eventsWhy, what replaces it, and the last day of the dual-fire window (a date runs to the end of that day, UTC)
externalNotrue when another service sends it, so no call in this repo isn't "dead"
yaml
# 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?

CommandWhat it does
datalyze initSets 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-auditRuns the install audit on a checkout: registry, baseline, typed tracker and report
datalyze init-registryCreates an empty registry with the Datalyze default conventions
datalyze validateChecks the registry against the hard rules
datalyze scanCompares tracking calls in the code with the registry
datalyze generateWrites 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|warehouseBuilds the data snapshot from Mixpanel, PostHog, Amplitude, or SQL on BigQuery, Snowflake or ClickHouse
datalyze auditCompares the plan, the code and what the analytics tool received
datalyze draftAdds a draft entry for every tracking call the registry doesn't know
datalyze diffWhat a branch changed about tracking, against --base
datalyze checkRuns the PR check: --pr <n> on GitHub, or the current branch against --base locally
datalyze rulesThe scripts-only check, datalyze/rules
datalyze actionThe GitHub Action's entry point (--job tracking, rules, sync-sheet, requests, watch or after-merge)
datalyze sync-sheetRebuilds the Google Sheet from the registry, keeping the human columns
datalyze after-mergeOn the default branch: moves what shipped from planned to implemented and marks signed-off proposals, as one pull request from datalyze/after-merge
datalyze requestsTurns new rows on the Sheet's Requests tab into proposal pull requests, and refreshes the Status of rows that have one
datalyze watchThe 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 fixOpens fix pull requests, one per area, for chosen audit findings, through the PR check
datalyze import-sheetWrites a registry from an existing tracking-plan Sheet: --csv exports of its tabs, or --tabs read from the connected Sheet
datalyze github-app createCreates your own GitHub App so the agent's commits run your CI
datalyze workflowWrites .github/workflows/datalyze.yml for an action reference
datalyze method bundleBuilds the method bundle from a method folder, and signs it with --sign-key
datalyze method releaseSnapshots the method folder as a new version, if it changed
datalyze baselineAccepts today's findings as legacy debt (analytics owner only)
datalyze vocabularyRebuilds _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?

OptionCommandWhat it does
--since-baselinevalidateOnly findings added since _baseline.json
--changed-files <f>scanLimit drift to what a change touched (git diff --name-only output)
--fail-on-driftscanExit 1 when code and registry disagree
--update-statusscanOn the main branch only: planned to implemented, and refresh code_refs
--checkgenerateExit 1 if the typed tracker is out of date
--base <ref>diff, checkThe ref to compare against
--snapshot <file>audit, draft, install-audit, watchThe data snapshot
--events, --area, --since, --prauditThe audit's scope
--applyauditWrite the statuses the data proves, and _audit.json for the Sheet
--project-id, --region, --host, --days, --cache, --outsnapshotThe 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, --schemasnapshot warehouseOverride analytics.warehouse and analytics.source from .datalyze/config.yaml
--pr <n>, --repo <owner/name>check, rules; --repo also github-app create, requests, watch and fixThe GitHub pull request and repo
--comment <text>, --as <login>check, locallyAdd a reviewer's comment first, such as /datalyze approve
--method-dir <dir>check, rules, install-audit, requestsRead the method from a folder instead of the licensed bundle (development)
--job <job>actiontracking, rules, sync-sheet, requests, watch or after-merge
--items <names>fixThe audit items to fix, comma-separated
--audit <file>fixThe audit file to read buckets and checks from (default the registry's _audit.json)
--source <text>fixWhere the findings came from, such as "Watch PR #12"
--dry-runsync-sheet, after-mergeShow what would change without writing to the Sheet or opening a pull request
--pending-dir <dir>sync-sheetFill the Pending Changes tab from the proposal files in this folder
--csv <files>, --tabs <names>import-sheetCSV exports of the old Sheet's tabs, comma-separated, or tab names to read from the connected Sheet
--org <org>, --name <name>github-app createCreate the App under an organisation, with this name
--version <v>, --dir <dir>, --sign-key <file>, --out <file>method bundleThe 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 flagsinitOne per setup answer, plus --yes and --dry-run; run datalyze init --help
--local, --now <time>watchWatch the pull requests checked locally (merge commits naming #n); run as of a given time

Which pull request comment commands are there?

CommandWhat it does
/datalyze approveApproves every pending additive, high-confidence item
/datalyze approve p1,p3Approves 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 p2Drops an item after it failed verification
/datalyze choose p1 BAnswers 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 retryRuns analysis or verification again, including a run stuck on analysing or implementing
/datalyze signoffSigns 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?

yaml
# .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.