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'));
{
"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');
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');
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');
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:
Turn on curation for the KB:
SELECT aidb.update_semantic_kb_curation('bank_kb', true);
Outputupdate_semantic_kb_curation -------------------------------------- {"current": true, "previous": false} (1 row)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 erroroutcomeis"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.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'));
decisionis'approve'or'reject'. Once approved, the live comment updates and the KB reconciles it, the same as an uncurated write.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 withaidb.add_relationship().aidb.accept_relationship_candidates()accepts only candidates derived from query history, andresolve_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'sdescription.
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');
ERROR: Invalid parameters: mode 'bogus' is not one of 'add' or 'edit'
SELECT aidb.resolve_object_comment('bank_kb', 'bank', 'events', 'maybe');
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');
ERROR: Invalid parameters: bank.events has no comment awaiting a decision
Next steps
- See how a crawled relationship's
descriptioncomes 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.