Invoking agents#
aidb.agent_converse() sends a prompt to an agent and runs its
reasoning loop — think, optionally call tools, observe, repeat — until
it produces an answer or hits a limit:
SELECT * FROM aidb.agent_converse(
agent_name => db_helper,
prompt => How many rows are in the orders table?,
conversation_id => NULL, -- optional; continue an existing conversation by id. Default: NULL (starts a new conversation)
read_only => NULL, -- optional; force read-only mode on/off. Default: NULL (auto: on for a read replica, off otherwise)
output_type => NULL, -- optional; override the agents structured output schema for this call. Default: NULL (use the agents own configuration)
debug => NULL -- optional; also emit every action as a NOTICE. Default: NULL (false)
);
__OUTPUT__
message | conversation_id | error
- ------------------------------------+--------------------------------------+-------
The orders table has 4,213 rows. | 8f14e45f-ceea-467e-9575-a3d1a2c1a123 |
(1 row)
Like Managing agents , this never raises for an agent-logic failure (an
unknown agent, a mid-loop reasoning failure, a budget that was exceeded)
— error is populated on the returned row instead, so the call’s
transaction (and everything already logged for that attempt) still
commits.
Pass output_type to override the agent’s configured output schema
for just this call — see Structured output for how to build one.
Conversations#
Every agent_converse call belongs to a conversation, identified by
conversation_id . Omit it on the first call and AIDB generates one;
pass that same id back in on later calls to continue the conversation,
with the agent’s own prior history as context:
SELECT * FROM aidb.agent_converse(db_helper, And how many are still pending?,
conversation_id => 8f14e45f-ceea-467e-9575-a3d1a2c1a123);
If you’d rather have the id in hand before sending a first prompt (for
example, to log it alongside a UI session), mint one upfront with
aidb.start_agent_session() :
SELECT * FROM aidb.start_agent_session(db_helper);
SELECT * FROM aidb.agent_converse(db_helper, How many rows are in the orders table?,
conversation_id => <the id start_agent_session returned>);
start_agent_session only validates the agent name and hands back a
fresh id. A conversation doesn’t actually exist until the first
agent_converse call that uses it.
Retrieving conversations#
aidb.get_conversation() returns a conversation’s user prompts and
final answers, in order. It does not return tool calls or intermediate
reasoning steps, which are activity details rather than “the
conversation”:
SELECT * FROM aidb.get_conversation(8f14e45f-ceea-467e-9575-a3d1a2c1a123);
__OUTPUT__
message_id | task_id | action_type | role | sender_id | contents
- -----------+---------+--------------+-------+-----------+---------------------------------------
... | ... | user_prompt | user | postgres | How many rows are in the orders table?
... | ... | answer | agent | db_helper | The orders table has 4,213 rows.
(2 rows)
aidb.get_message() retrieves one message’s plain-text contents by
its message_id . Both are backed by the aidb.conversation_log
view, which you can also query directly; aidb.conversations
aggregates it into one row per conversation (message count, status, last
message). See Agents reference for their full column lists.
Read-only mode#
Pass read_only => true to run an agent without persisting anything —
no conversation history is written, and conversation_id always comes
back NULL , so a read-only run can’t be resumed later. It’s
automatic (no need to pass it explicitly) when the database is a read
replica.
A read-only run enforces two things at once: every tool call executes
under SET LOCAL transaction_read_only = on for that statement, and
any tool that isn’t provably read-only is excluded from what the agent
can even see or call in the first place. MCP tools are always excluded
in read-only mode, regardless of how they’re classified — an external
server’s own read/write behavior isn’t something AIDB can verify. If a
Delegation happens during a read-only run, the delegate’s own
sub-call is also forced read-only.
Debug mode#
Pass debug => true to also emit every action — model
requests/responses, tool calls and their results, reasoning steps — as a
Postgres NOTICE as it happens, in addition to (not instead of) the
normal history written to aidb_internal.action_log . This is useful
for watching an agent work interactively (psql prints NOTICE to
the terminal by default) or for debugging why it took the path it did.
SELECT * FROM aidb.agent_converse(db_helper, How many rows are in the orders table?, debug => true);
NOTICE: [tool_call] run_sql_query({"query": "SELECT count(*) FROM orders"})
NOTICE: [tool_response] run_sql_query -> [{"count": 4213}]
NOTICE: [answer] The orders table has 4,213 rows.
Each NOTICE includes that action’s request/response payload —
prompts, tool arguments, and results — truncated but not redacted.
Avoid debug => true in contexts where those details (which may
include data returned by your own tools) shouldn’t be visible to whoever
can see the client output or server log. It applies to any delegate
sub-calls the run makes, too.
Budgets and limits#
Each agent_converse call is capped by four budgets, all optional and
set on aidb.create_agent :
Parameter |
What it caps |
|---|---|
input_token_budget |
Input tokens for the call. |
output_token_budget |
Output tokens for the call. |
max_iterations |
Reasoning rounds — one model call plus any tools it calls in that round, not individual tool calls. A hard ceiling of 25 always applies, regardless of configuration, as a backstop against a runaway agent. |
timeout |
Wall-clock seconds for the call. Defaults to 300 seconds if unset. |
What happens when a budget is exceeded depends on budget_strategy :
Strategy |
Behavior |
|---|---|
attempt_complete (default) |
Grants 3 extra reasoning rounds once a budget is first exceeded, telling the model its time is running out so it can wrap up — then halts with an error if it still hasn't finished. |
error |
Halts immediately with an error. |
summarize |
Stops immediately and asks the model for a brief status report of what it had done so far, returning that as the answer instead of an error. |
ignore |
Logs a one-time warning (as a Postgres WARNING) and keeps going with no limit. |
Context length#
Long conversations are bounded automatically: once a task’s assembled
prompt would exceed an internally tracked size estimate, AIDB asks the
model to summarize the older part of the history and continues with that
summary in place of the original turns. This is basic, automatic
bounding to keep long-running conversations from blowing past a model’s
context window — there’s nothing to configure. A compaction entry
appears in the activity log when this happens (see Debug mode , or
query aidb_internal.action_log directly for the full activity
history behind a conversation — every model request/response and tool
call/response, not just the user-facing messages
aidb.get_conversation() returns).