Docs
Relay Docs View as Markdown

Declarations

The yaml contract that defines a tracker: fields, states, verbs, guards, and display, reviewed like code and enforced by the engine on every write.

Schema as a reviewed change#

A tracker is born from a declaration: one yaml file, owned by the agent that runs the tracker, shipped through the same review-and-merge governance as code. Applying a declaration is validated end to end; a declaration that names a field type, renderer, or section the engine does not ship is refused at the door with a precise error, never silently degraded. A typo cannot become a production surface.

This is the property that makes the engine safe to hand to a workforce that ships fast: the schema is a diff a reviewer can read, the validator is the same code that enforces writes, and these docs' own vocabulary tables are generated from that validator, so contract, enforcement, and documentation cannot disagree.

Anatomy of a declaration#

A declaration carries, at minimum: a slug (the surface URL), a code (the reference-code prefix), a title and description, an owner, and its fields, states, and verbs. Most trackers add a guide (the cold-reader explainer rendered beside the rows), visibility, and display.

Fields#

Each field declares a key, a type, and optionally a label, required, and type-specific attributes (an enum's options, a reference's target tracker:).

Type What it holds
string Short single-line text.
text Long-form text. With render: markdown it renders as prose.
enum One value from a closed set declared on the field, each option carrying a tone chip.
int A whole number.
decimal A decimal number.
bool Yes or no.
datetime A point in time, stored UTC and rendered in the viewer's timezone.
url A link, rendered clickable.
person A workspace human, resolved against the directory.
agent A fleet agent, resolved against the roster.
file One attached file, served with fetch-time authorization and an access log.
file_list A list of attached files.
image_list A list of images, rendered inline on the row.
row_ref A reference to another row, by full reference code. Same tracker by default; a tracker: attribute on the field makes it cross-tracker, and the target renders as a live card.
thread_ref A reference to a conversation thread.
json Structured data. Pair with a render hint (keyvalue, list, chips, code) to shape the cell.

Structured cells take a render hint, so a JSON cell arrives shaped rather than raw:

Hint How the cell renders
markdown Render the text as formatted prose.
keyvalue Render object keys and values as a two-column list.
list Render array items as a bulleted list.
chips Render array items as compact chips.
code Render preformatted, monospaced.

States and tones#

States declare the row lifecycle. Each state (and each enum option) can carry a tone, which is how a row communicates health at a glance without anyone reading it:

Tone What it signals
neutral No signal: the resting state.
info Informational: in motion, no judgment.
success A good outcome (resolved, verified, done).
warning Needs attention before it becomes a problem.
danger A bad outcome or a blocking state.

Transitions can declare requires_fields: the fields that must be present before a row may enter that state. A bug cannot reach resolved without its "what changed for you" line; a review cannot close without its verdict. The guard lives in the engine, so it holds for every writer through every door.

Verbs#

Verbs are the tracker's closed action vocabulary. Each declares its effect and who may run it:

Verb effects (what a verb does to the row):

Effect What it does
transition Move the row to a named target state, optionally requiring fields at the door.
patch Update named cells without changing state.
link Attach a reference (a thread, a row) to the row.

Who tokens (who may run a verb):

Token Who that is
owner-agent Only the tracker's owning agent, through its own authenticated gateway identity.
admin Workspace admins, acting in the console under their own session.
any-member Any signed-in workspace member.

A verb call outside the declared vocabulary, with a wrong enum value, or from a caller outside the declared audience refuses loudly with the reason. Silent no-ops are treated as defects in Relay, not conveniences.

Renderers and display#

Most trackers use the default renderer; specialized shapes are named renderers from a closed set:

Row renderers (renderer: on the declaration):

Renderer The shape it draws
generic-v1 The default: a filterable row list with a detail pane composed from the declared sections.
staged-gate-v1 A stage spine across the top of each row detail: ordered stages, each empty, in progress, or done.
report-v1 A run-ledger report: periodic rows rendered as a dense typed-column table.

Dashboard renderers (renderer: on a declared dashboard):

Renderer The page it composes
seat-custody-v1 The seat-custody board: who holds which Claude seat, utilization, and custody events, composed as one page.

The report renderer's dense table cells come from their own closed vocabulary:

Column type What it renders
id The row's reference code, linked.
text Plain text.
percent A percentage.
meter A small horizontal meter.
bar_money A money amount with a proportional bar.
number A formatted number.
money A money amount.
money_cents A money amount stored in cents.
split A two-part value rendered as a split cell.
share_bar A proportional share bar across the row set.
multiple_or_dash A multiplier, or a dash when absent.

Charts are declared under display.charts; the kinds are enumerated in The tracker model.

A real declaration#

The worked example below is not a sample: it is the live declaration of our own bug tracker, re-emitted from the engine's stored copy at render time, so what you read here is what the engine is enforcing right now. It shows the full grammar in one place: typed fields (including person, enum with tones, image_list, url), a guarded lifecycle, a cold-reader guide, and display configuration.

code: GBUG
slug: gtos-bug-reports
guide: "Every **Report a bug** button on a GTOS surface (the console and every\nagent dashboard) files a row here. The runtime triages around the clock:\nit diagnoses, dedupes, DMs the reporter a receipt, and opens a per-bug\ntriage thread, so a `triaged` row already carries a diagnosis before any\nhuman looks.\n\n**Fix route** says whose hands fix it: `agent` rows the runtime ships\nitself; `terminal` rows wait for one of Norman's terminal sessions (the\nsweep); `product` rows belong to Gotrade product repos and get routed out.\n\n**States** are the lifecycle: `new` → `triaged` → one of `resolved`\n(always with a \"what changed for you\" line; the reporter's DM leads with\nit), `wont_fix`, or `duplicate`. A report that turns out to be a feature\nask converts: the work gets its FR and the row stays OPEN, wearing the\ndestination in **Tracks** and resolving when it ships (a weekly watcher\nfollows the destination; `routed` is retired since 2026-08-08 and only\nolder rows wear it). A `triaged` row can also be shelved to `backlog`:\nvalid but deliberately deprioritized on Norman's explicit ruling, reason\non the row. No resurface date (unlike a snooze), and reversible: a later\nsweep can pull it back to `triaged`. The **disposition chip** on an open\nrow is the other axis (who has it and why it waits), explained in the\nlegend below.\n\n**Module** is the product area the defect lives in (nine values, from\n`messaging` to `mobile-shell`; the canonical vocabulary is the console's\nfeature inventory, served at /api/product/modules). Pre-guessed from the\npage URL at filing, confirmed or corrected at triage: it is the sweep's\ngrouping axis, and the facet that answers \"show me the apps-engine\nqueue\". ENGINE ASKS (requests to expand the tracker engine itself: a new\nchart kind, verb semantics, a renderer) file as ordinary bugs from /apps\npages and wear `module: apps-engine` (the 2026-08-08 T8 amendment).\n\nReporters: your receipt DM tracks the row for you; you'll hear there the\nmoment the fix ships. The triage thread linked on each row is where the\ninvestigation lives."
owner: dario-soh
title: 'GTOS bug reports'
verbs:
  -
    key: set-status
    who:
      - owner-agent
      - admin
    effect: transition
  -
    key: annotate
    who:
      - owner-agent
      - admin
    effect: patch
    fields:
      - triage_note
      - fix_route
      - module
  -
    to: duplicate
    key: mark-duplicate
    who:
      - owner-agent
      - admin
    effect: transition
    requires:
      - duplicate_of
fields:
  - key: description
    type: text
    label: 'What happened'
    required: true
  - key: reporter
    type: person
    required: true
  - key: source_app
    type: string
    label: Surface
  - key: module
    type: string
    label: Module
  - key: page_url
    type: url
    label: Page
  - key: app_version
    type: string
    label: 'App version'
  - key: user_agent
    type: string
    label: Browser
  - key: viewport
    type: string
  - key: screenshots
    type: image_list
  - key: attachments
    type: file_list
  - key: triage_note
    type: text
    label: 'Triage note'
  -
    key: fix_route
    type: enum
    label: 'Fix route'
    tones:
      agent: info
      product: neutral
      terminal: warning
    options:
      - agent
      - terminal
      - product
  - key: routed_to
    type: string
    label: 'Tracks (FR / PR)'
    linkify: true
  - key: user_impact
    type: string
    label: 'What changed for you'
  - key: backlog_reason
    type: string
    label: 'Backlog reason'
  - key: duplicate_of
    type: row_ref
    label: 'Duplicate of'
    no_chains: true
  - key: triage_thread
    type: thread_ref
    label: 'Triage thread'
  - key: receipt_dm
    type: thread_ref
    label: 'Receipt DM'
  - key: resolved_at
    type: datetime
    label: 'Fix shipped'
states:
  - key: new
    tone: warning
    initial: true
  - key: triaged
    tone: info
  -
    key: backlog
    tone: neutral
    requires_fields:
      - backlog_reason
  -
    key: resolved
    tone: success
    terminal: true
    stamp_on_enter: resolved_at
    requires_fields:
      - user_impact
  - key: wont_fix
    tone: neutral
    terminal: true
  -
    key: duplicate
    tone: neutral
    terminal: true
    requires_fields:
      - duplicate_of
  -
    key: routed
    tone: info
    terminal: true
    requires_fields:
      - routed_to
display:
  list:
    - code
    - description
    - state
    - fix_route
    - source_app
    - claim
    - reporter
    - created_at
  charts:
    - x: created_at
      key: intake
      kind: bar
      split: fix_route
      title: Intake
      bucket: week
    - x: created_at
      key: by-surface
      kind: heatmap
      scale: global
      split: source_app
      title: 'Intake by surface'
      bucket: week
    - x: state
      key: by-state
      kind: donut
      title: 'By state'
    -
      x: resolved_at
      'y':
        to: resolved_at
        agg: avg_days
        from: created_at
      key: turnaround
      kind: line
      title: 'Resolution turnaround'
      bucket: week
    - key: open
      kind: burndown
      title: 'Open over time'
      bucket: week
  detail:
    lead: description
    meta:
      - reporter
      - source_app
      - module
      - fix_route
      - triage_note
      - user_impact
      - backlog_reason
      - page_url
      - app_version
      - viewport
      - user_agent
      - resolved_at
    links:
      - triage_thread
      - receipt_dm
      - duplicate_of
      - routed_to
    media:
      - screenshots
      - attachments
  density: dense
  filters:
    - disposition
    - module
    - fix_route
    - source_app
    - reporter
  renderer: generic-v1
  dense_list:
    - code
    - description
    - state
    - fix_route
    - source_app
    - module
    - reporter
    - page_url
    - resolved_at
    - created_at
section: operations
workflow: workflow-gtos-bug-report-intake
visibility:
  class: restricted
  audience:
    roles:
      - owner
      - admin
description: 'User-filed bugs on GTOS fleet and subsystem surfaces, triaged and driven to resolution by Dario.'
capabilities:
  claim: true
  snooze: true

Evolving a schema#

Additive changes are a yaml edit: add the field, ship the PR, and new writes carry it while old rows render an honest gap. Renames and removals are deliberate, reviewed events, and because every row is soft-deleted and every write audited, no schema change can silently destroy history. When a tracker outgrows the engine (real joins, computation, bespoke logic), the substrate ladder names the exit ramp: records migrate to a typed home with their history, and the engine keeps doing what it is for.