Skip to content

feat(dbt): add column-level meta to AGENTS.DBT_COLUMN - #36

Merged
kevinskim93 merged 4 commits into
mainfrom
feat/dbt-column-meta
Aug 11, 2026
Merged

feat(dbt): add column-level meta to AGENTS.DBT_COLUMN#36
kevinskim93 merged 4 commits into
mainfrom
feat/dbt-column-meta

Conversation

@kevinskim93

@kevinskim93 kevinskim93 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Why

Customer request: bring column meta into the dbt_column spec.

Column-level meta is where dbt projects record governance and semantic context — PII flags, data owners, units, business-friendly labels, masking policies. AGENTS.DBT_MODEL already exposes model-level meta, so an agent can see that context for a table but not for the individual columns it is about to query.

What changed

AGENTS.DBT_COLUMN gains one meta TEXT column holding the column's dbt meta dict serialized as a JSON string, mirroring AGENTS.DBT_MODEL.meta exactly.

CREATE OR REPLACE TABLE AGENTS.DBT_COLUMN (
  model_id    VARCHAR NOT NULL,
  column_name VARCHAR NOT NULL,
  data_type   VARCHAR,
  description TEXT,
  meta        TEXT,          -- new
  PRIMARY KEY (model_id, column_name)
);

Precedence is the same rule DBT_MODEL.meta uses — column.config.meta, else top-level column.meta, else {}.

  • src/agents_schema/dbt.py — new schema column plus one row value in _ingest
  • SPEC.md — DDL, source-field table, and the summary-table description
  • the three built-in analyst skills — meta added to the dbt_model / dbt_column schema-reference rows, so agents know the field is queryable (the dbt_model row was already missing it)
  • tests/test_dbt.py — new; covers meta from config.meta, from top-level meta, and {} when absent

Why read both config.meta and top-level meta

Verified empirically rather than assumed, by parsing a project with dbt 1.12 that declares column meta both ways, and by diffing the published manifest JSON schemas:

manifest v11 (dbt ≤ 1.9) manifest v12 (dbt 1.10+)
ColumnInfo.config absent present, meta mirrored
ColumnInfo.meta present — the only source present, mirrored

On current dbt the two locations are always populated identically, whichever YAML form the project author uses (config: meta:, top-level meta:, or dbt_project.yml +meta:) — the same is true at model level. So on v12 either read alone would do. The top-level fallback is what keeps pre-1.10 manifests working, where column config does not exist at all; reading config first is forward-compatible with dbt's deprecation of the legacy top-level field. Neither branch is dead across the supported range.

Compatibility

Tables are CREATE OR REPLACEd on every run, so the column appears on the next sync with no migration. Queries that name columns explicitly are unaffected; SELECT * consumers get one extra field. Type mapping verified across all three writers: Snowflake TEXT, BigQuery STRING, Databricks STRING.

Scoped to meta only. Column-level tags also exist in the manifest and would be a natural follow-up, but were left out of this change.

Drive-by

tests/test_connector_root.py called omni.run without importing omni, so that test errored with NameError on every run — the only failure in the suite. Fixed in a separate commit.

Verification

Full suite:

$ uv run python -m unittest discover -s tests
Ran 133 tests in 0.829s
OK

End-to-end against a real dbt 1.12 manifest (not just the fixture), with order_id declaring meta under config: and customer_email at the top level:

('model.metatest.orders', 'order_id',       '', 'Primary key.',  '{"pii": false}')
('model.metatest.orders', 'customer_email', '', 'Billing email.', '{"pii": true}')

🤖 Generated with Claude Code

kevinskim93 and others added 2 commits July 31, 2026 10:22
Column meta is where dbt projects record governance and semantic context
(PII flags, ownership, units, business labels). AGENTS.DBT_MODEL already
exposes model-level meta; agents reading DBT_COLUMN had no way to see the
same context at column granularity.

Serialize each column's meta dict to a JSON string, using the same
precedence as DBT_MODEL.meta: config.meta, then top-level meta, then {}.
dbt 1.10 and later nest column meta under config while earlier projects
declare it at the top level, so both are read.

Also list meta in the three built-in analyst skills' schema reference for
dbt_model and dbt_column, so agents know the field is queryable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test_omni_run_upserts_root_before_source_tables called omni.run without
omni being imported, so it errored with NameError on every run. Unrelated
to the dbt change, but it was the only failure in the suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verified against a real dbt 1.12 manifest that column meta and
config.meta are always mirrored, so the spec's extra sentence about
1.10 nesting implied they were alternatives when they are not. Drop it
and match the terse one-line style of the DBT_MODEL.meta row.

Replace the dynamic meta-index lookup in the test with tuple unpacking,
matching the positional style used in test_sigma and failing loudly if
the row shape changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@smogili2
smogili2 self-requested a review July 31, 2026 22:49
Comment thread src/agents_schema/dbt.py Outdated
Column("column_name", "varchar", nullable=False),
Column("data_type", "varchar"),
Column("description", "text"),
Column("meta", "text"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since meta is always a JSON object, this can be marked as variant data type instead to make it easier for agents to know.

Comment thread tests/test_dbt.py
self.calls.append(("upsert", table.name, list(rows)))


class DbtColumnMetaTests(unittest.TestCase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also include dbt model meta column ingest test case.

abhijeethp
abhijeethp previously approved these changes Aug 11, 2026
meta is always a JSON object (config.meta/meta, defaulting to {}), so a
TEXT column holding a serialized string forces every consumer to guess
that it's JSON before it can query into it. Add a json column kind,
distinct from the existing array kind (which is hard-typed as list<string>
on BigQuery/Databricks and would corrupt a dict), mapped to each
warehouse's native semi-structured type: Snowflake VARIANT (via the same
PARSE_JSON binding tags already uses), BigQuery JSON, Databricks VARIANT
(via parse_json).

DBT_MODEL.meta and DBT_COLUMN.meta both move together so the same field
doesn't disagree in type across sibling tables. _ingest now passes the
raw meta dict through instead of pre-serializing it with json.dumps,
mirroring how tags already flows as a raw list and letting each writer's
binding layer own the wire format.

Updates SPEC.md's DDL and source-field rows for both tables, and the
DBT_MODEL/DBT_COLUMN row fixtures in test_agents_schema_writer.py (which
predate this and encode meta as an already-serialized string). Restores
tests/test_dbt.py, which had been deleted from the working tree, with
assertions matching the new raw-dict convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kevinskim93
kevinskim93 merged commit 35d8539 into main Aug 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants