Descriptions and curation v7

A well-commented schema produces a far more useful semantic KB — see Indexing your schema. You can always write those comments with plain COMMENT ON, but aidb.add_comment_to_object() does the same write and reconciles every knowledge base that indexes the object, so the new text is searchable immediately, without waiting for a crawl.

A semantic KB can add, protect, and remove descriptions safely — resolving concurrent edits and auditing every change along the way — and, if you turn on curation, hold each write for review before it goes live.

This applies to any table, view, column, or constraint (object_type => 'table' | 'view' | 'column' | 'constraint') — it's a general knowledge-base capability, not something specific to relationships. Commenting on a constraint is also how you give a crawled relationship a business-readable name. See Relationships and comment curation.

Writing a description

Call aidb.add_comment_to_object() with the schema, object name, and comment text to write a description and reconcile every KB that indexes the object in one step:

SELECT jsonb_pretty(aidb.add_comment_to_object(
  'bank', 'transactions', 'A movement of money against an account.', kb_name => 'bank_kb'));
Output
 {
     "current": "A movement of money against an account.",
     "outcome": "applied",
     "previous": null,
     "knowledge_bases_reconciled": [
         "bank_kb"
     ]
 }

knowledge_bases_reconciled names every KB updated by the write — an object can be indexed by more than one.

Breaking change at 7.7.0

mode defaults to 'add', and 'add' now refuses when a description already exists, rather than overwriting it silently:

SELECT aidb.add_comment_to_object('bank', 'transactions', 'Another go.', kb_name => 'bank_kb');
Output
ERROR:  Invalid parameters: bank.transactions already has a description, and mode is 'add'.
Current: "A movement of money against an account.". To replace it, call again with mode => 'edit'

Callers that relied on the old overwrite behavior must pass mode => 'edit' explicitly:

SELECT jsonb_pretty(aidb.add_comment_to_object('bank', 'transactions',
  'A movement of money against an account, positive or negative.',
  kb_name => 'bank_kb', mode => 'edit'));

Protecting existing descriptions

A description AIDB didn't write is treated as a person's work — mode => 'edit' alone won't overwrite it:

SELECT aidb.add_comment_to_object('bank', 'customers', 'Our retail banking customers.',
  kb_name => 'bank_kb', mode => 'edit');
Output
ERROR:  Invalid parameters: bank.customers has a description this system did not write, so it is
treated as a person's. Pass force => true to replace it. Current: "People who bank with us."
SELECT jsonb_pretty(aidb.add_comment_to_object('bank', 'customers', 'Our retail banking customers.',
  kb_name => 'bank_kb', mode => 'edit', force => true));

Handling concurrent edits

Pass what you last read as expected_comment, and the write fails if someone changed the description in the meantime — optimistic concurrency without a lock:

SELECT aidb.add_comment_to_object('bank', 'customers', 'Newer text.',
  kb_name => 'bank_kb', mode => 'edit', expected_comment => 'Something else');
Output
ERROR:  Invalid parameters: the description has changed since it was read. Expected "Something else",
found "Our retail banking customers."

Removing a description

aidb.remove_comment() clears a description the same way add_comment_to_object() writes one, and it's the direct route: it doesn't need a mode argument, since there's nothing to protect against once the comment is gone.

SELECT jsonb_pretty(aidb.remove_comment('bank', 'transactions', kb_name => 'bank_kb'));

remove_comment() takes the same expected_comment and force protections as add_comment_to_object(), so a description a person wrote still needs force => true to remove.

Auditing changes

Every semantic KB operation is recorded in an audit log. For a description write, the record includes whether the write was forced and what it discarded. A superuser or the extension owner reads the log with aidb.semantic_kb_audit():

SELECT ts, op, func, actor_name, object_ref, detail
FROM aidb.semantic_kb_audit(kb_name => 'bank_kb', op => 'write')
ORDER BY ts DESC LIMIT 3;

op groups operations into retrieve, execute, annotate, write, propose, approve, reject, and admin. func is the function that ran. caller_kind is agent_hub, mcp, or direct. See aidb.semantic_kb_audit for all parameters and columns.

Nothing deletes audit records automatically. aidb.semkb_audit_prune(kb_name, older_than) deletes the records of one knowledge base that are older than older_than. If you omit older_than, the aidb.semkb_audit_retention parameter applies (default 30 days). Run it as an operator or from a scheduled job such as pg_cron. The caller must be a member of the knowledge base's owner role.

Reviewing before it's live

With curation on for a KB, enabled by calling aidb.update_semantic_kb_curation(kb_name, true), a write doesn't take effect. It queues for review instead:

  1. Turn on curation for the KB:

    SELECT aidb.update_semantic_kb_curation('bank_kb', true);
    Output
         update_semantic_kb_curation
    --------------------------------------
     {"current": true, "previous": false}
    (1 row)
  2. Write a description as usual. With curation on, it queues instead of applying:

    SELECT jsonb_pretty(aidb.add_comment_to_object(
      'bank', 'accounts', 'An account a customer holds with the bank.', kb_name => 'bank_kb'));
    Output
     {
         "current": null,
         "outcome": "queued",
         "previous": null,
         "proposed": "An account a customer holds with the bank.",
         "knowledge_bases": [
             "bank_kb"
         ]
     }
    Check outcome, not just the absence of an error

    outcome is "queued", not "applied", and the live comment is unchanged. A caller that only checks for the absence of an error will believe the write succeeded.

  3. Review the queue and approve or reject the proposal:

    SELECT object, object_type, proposed_comment, proposed_by FROM aidb.list_proposed_comments('bank_kb');
    
    SELECT jsonb_pretty(aidb.resolve_object_comment('bank_kb', 'bank', 'accounts', 'approve'));

    decision is 'approve' or 'reject'. Once approved, the live comment updates and the KB reconciles it, the same as an uncurated write.

  4. Turn curation back off, the same way you turned it on:

    SELECT aidb.update_semantic_kb_curation('bank_kb', false);
resolve_object_comment and update_semantic_kb_curation are deliberately SQL-only

Neither is registered as a native agent tool — see Native tools. An agent holding either could approve its own proposal or switch off the review that governs it, so approving a proposal and toggling curation both stay with a person, by design.

Relationships and comment curation

Comment curation and relationships are separate features that don't gate each other, even though the two can look related at a glance:

  • update_semantic_kb_curation() controls comment writes only. Relationship writes never consult it.
  • A relationship an agent records is always stored as status = 'proposed', regardless of the curation setting. A person approves it by re-asserting it with aidb.add_relationship(). aidb.accept_relationship_candidates() accepts only candidates derived from query history, and resolve_object_comment() doesn't apply to relationships.
  • object_type => 'constraint' is how a person attaches a business-readable description to a relationship the crawl found — see Verifying what the crawl found, where a constraint's comment becomes that relationship's description.

This writes the description the crawl example shows already attached to accounts_cust_id_fkey:

SELECT jsonb_pretty(aidb.add_comment_to_object(
  'bank', 'accounts', 'Every account belongs to exactly one customer.',
  object_type => 'constraint', constraint_name => 'accounts_cust_id_fkey', kb_name => 'bank_kb'));

The same call accepts object_type => 'column' with column_name to comment on a column instead.

Common errors

There are no custom SQLSTATEs on this surface — match on message text, and most of it carries the Invalid parameters: ... prefix. Three of these are covered where they come up: mode => 'add' refusing an existing description (see Writing a description), a protected description refusing an overwrite (see Protecting existing descriptions), and a stale expected_comment refusing a concurrent edit (see Handling concurrent edits).

mode and decision are controlled vocabularies too, validated at the point they're read, with the allowed values listed in the error:

SELECT aidb.add_comment_to_object('bank', 'events', 'x', kb_name => 'bank_kb', mode => 'bogus');
Output
ERROR:  Invalid parameters: mode 'bogus' is not one of 'add' or 'edit'
SELECT aidb.resolve_object_comment('bank_kb', 'bank', 'events', 'maybe');
Output
ERROR:  Invalid parameters: decision 'maybe' is not one of 'approve' or 'reject'

A valid decision still fails if there's nothing to decide on:

SELECT aidb.resolve_object_comment('bank_kb', 'bank', 'events', 'approve');
Output
ERROR:  Invalid parameters: bank.events has no comment awaiting a decision

Next steps

  • See how a crawled relationship's description comes from a constraint comment in Relationships.
  • Review the full search surface, including proposed content, in Searching a semantic KB.
  • Look up every curation function's full parameters and return columns in the reference.