COMMENT conventions
Kozou reads the COMMENT you attach to tables, columns, views, and
foreign keys and turns it into three surfaces: an Admin UI, a REST
API, and MCP context for AI agents. Most of a COMMENT is plain prose
meant for humans, and Kozou carries that prose through untouched. But
a small, opt-in set of @-prefixed tags lets you add structure —
widget hints, AI guidance, business policy, and example queries —
without leaving SQL.
This page documents that tag vocabulary exactly as @kozou/core
parses it. For how the parsed result lands in each surface, see
Emitted surfaces. For why the database
is the single source for all of this, see
Postgres as source of truth.
The model: prose first, tags as opt-in structure
Section titled “The model: prose first, tags as opt-in structure”A COMMENT is plain text. You write it for a human reader, and the
first line should be short — Kozou uses it as a fallback label. Below
that prose, you may add tag lines. A tag line is recognized when it
starts (after optional leading whitespace) with @, a tag name, and
a colon:
@<name>: <value>Four tag names are recognized: @ai, @widget, @policy, and
@example. Each is parsed differently, and the choices below are
deliberate — some tags stay in the displayed body, some are removed,
and one is lifted out entirely. The sections that follow cover each
tag and what happens to a tag Kozou does not recognize.
Tags are recognized at line start only. A tag written mid-line —
the natural way to jot a short COMMENT — is not parsed, and not
removed either: the literal tag text stays in the description and
flows verbatim into every surface built from it (the OpenAPI document,
MCP describe output). The parser logs a warning when a known tag
appears mid-line, so the leak is not silent:
COMMENT ON COLUMN orders.notes IS 'Free-form notes. @widget: textarea';-- NOT parsed — the description stays-- "Free-form notes. @widget: textarea" (a parse warning is logged)
COMMENT ON COLUMN orders.notes IS 'Free-form notes.@widget: textarea';-- parsed: widget = textarea, description = "Free-form notes."The parser returns a single structured result per COMMENT:
type ParsedComment = { body: string; // human-facing text, with some tags removed ai: string[]; // every @ai value, in order widget: WidgetType | null; // the last valid @widget value policy: string[]; // every @policy value, in order examples: ExampleQuery[]; // each @example block, lifted out of body};@ai — free-text guidance for AI agents
Section titled “@ai — free-text guidance for AI agents”@ai carries instructions or context aimed at an AI agent rather than
at a UI. You can write more than one @ai line; each value is
collected, in order, into ai[]. A value may also continue onto
indented lines directly below its tag line — the whole block is
captured as one note.
COMMENT ON TABLE orders IS 'Customer orders. One row per placed order.
@ai: For revenue figures, prefer the view vw_orders_paid. @ai: Rows where deleted_at is not null are soft-deleted; exclude them.';Like @policy, @ai is both kept in the displayed body and
extracted (@widget is removed, and @example is lifted out
entirely). The reasoning is that an @ai note usually reads as a
sensible sentence to a human as well, so removing it would lose
context for anyone reading the raw description. The same text
therefore appears in body and in ai[].
@widget — a column-only UI widget hint
Section titled “@widget — a column-only UI widget hint”@widget tells the Admin UI which input control to render for a
column. It is meaningful only on a column COMMENT; on a table or
view COMMENT it has no column to bind to.
COMMENT ON COLUMN products.description IS 'Long-form product copy shown on the storefront. @widget: textarea';Three behaviors are specific to @widget:
- It is removed from the displayed body. Unlike
@ai, a@widgetline is structural noise to a human reader, so it does not appear inbody. - Last one wins. If a COMMENT contains more than one
@widgetline, the final valid value is the one that takes effect. - The value must be a known widget type. If the value is not one of the types below, Kozou logs a warning and records no widget for that column (so it falls back to the heuristic default).
The full set of widget types, exactly as defined in @kozou/core, is
these 12:
texttextareanumberbooleandatedatetimeenum-selectrelation-selectjsonimage-urluuidcurrency@widget is a hint, not the only input. When you omit it, Kozou
infers a widget from the column’s type and constraints (for example, a
column with a CHECK list of allowed values infers enum-select, and a
foreign-key column infers relation-select). A YAML UI Hints file, if
present, takes precedence over both the @widget tag and the
heuristic. image-url renders the image’s URL for display in v0.1.1;
it is not an upload control.
@policy — business-policy text (not enforced in v0.1)
Section titled “@policy — business-policy text (not enforced in v0.1)”@policy records a business rule in prose. Every @policy line is
collected into policy[], and — like @ai — the line is kept in
the displayed body.
COMMENT ON COLUMN orders.status IS 'Order lifecycle state. @policy: An order may only move to ''shipped'' after payment is captured. @widget: enum-select';In v0.1.1, @policy is parsed but not enforced. Kozou extracts
the text and makes it available as context; it does not generate
constraints, triggers, or API-layer checks from it. Treat it as
documentation that travels with the schema and reaches AI agents,
not as a guarantee the runtime upholds.
@example — example queries for views and concepts
Section titled “@example — example queries for views and concepts”@example attaches a runnable example query to a view (surfaced as a
“concept” in MCP). It is a multi-line tag with the most distinctive
parsing (@ai and @policy also continue onto indented lines below
their tag line, but @example is the only tag whose block is lifted
out of the body entirely):
- The text on the
@example:line itself is the description (it may be empty). - The indented continuation lines below it are the SQL.
- The block ends at the first non-indented line, the next
@-tag, or the end of the COMMENT. - The shared leading indentation is stripped, so the stored SQL reads as written rather than indented by the COMMENT block. Blank lines inside the block are preserved, so a multi-statement example keeps its separators.
COMMENT ON VIEW vw_orders_paid IS 'Orders that have been paid for.
@example: Total revenue in the last 30 days select sum(total_amount) from vw_orders_paid where paid_at >= now() - interval ''30 days'';';Unlike every other tag, an @example block is lifted out of the
displayed body entirely. The SQL is surfaced separately — as a list
of { description, sql } entries — because it is presented as
example queries, not as prose. From the COMMENT above, the body keeps
only Orders that have been paid for., and the example becomes one
entry whose description is Total revenue in the last 30 days and
whose sql is the dedented two-line query.
Unknown tags — kept in the body, logged as a warning
Section titled “Unknown tags — kept in the body, logged as a warning”If a COMMENT contains a tag Kozou does not recognize — say a future
@foo: — the parser does not fail. It keeps the line in the
displayed body and logs a warning. This is deliberate forward
compatibility: a COMMENT written for a newer Kozou still renders
sensibly under v0.1.1, and the unknown text is never silently
dropped.
COMMENT ON COLUMN products.sku IS 'Stock-keeping unit. @foo: reserved for a future convention';The @foo: line stays in body, and you will see a warning from
@kozou/core noting that an unknown tag was kept in the body.
A worked example
Section titled “A worked example”Put the rules together on a single generic column. Suppose you have a
products table with a status column constrained to
draft, published, or archived:
COMMENT ON COLUMN products.status IS 'Publication state of the product. draft: not yet visible published: live on the storefront archived: hidden but retained
@ai: Only ''published'' rows should appear in public listings. @widget: enum-select';Kozou parses that one COMMENT into the following pieces:
| Part of the COMMENT | Where it goes |
|---|---|
Publication state of the product. and the three draft/published/archived lines | body — the displayed description |
@ai: Only 'published' rows should appear in public listings. | kept in body and added to ai[] |
@widget: enum-select | removed from body; sets widget to enum-select |
So the displayed body reads as plain prose (the type description plus
the @ai sentence), the column renders with an enum select control in
the Admin UI, and the @ai guidance is available to AI agents through
MCP. One COMMENT, three surfaces.
If you had written @widget: dropdown instead, dropdown is not one
of the 12 known widget types, so Kozou would log a warning, leave
widget unset for that column, and fall back to the inferred default
(here, enum-select, because the column has a CHECK list).
How tags flow into each surface
Section titled “How tags flow into each surface”The same parsed result feeds all three of Kozou’s outputs:
- Admin UI (
@kozou/svelte-ui) —@widgetchooses the input control for a column; thebodytext becomes the field’s description. - REST API — the REST layer is
@kozou/api, Kozou’s in-house backend and the default since v1.0, served in-process bykozou dev. COMMENT prose is what describes your tables and columns, flowing into its OpenAPI document. An external PostgREST is the opt-out (kozou dev --adapter postgrest). - MCP context (
@kozou/mcp) —@ainotes,@policytext, and@examplequeries become structured context an AI agent can read, so the agent learns not just your table shapes but the guidance and example query paths you wrote alongside them.
For a surface-by-surface walkthrough of exactly what each output contains, continue to Emitted surfaces.