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:

.. code:: sql

   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 :ref:`Managing agents <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 :ref:`Structured output <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:

.. code:: sql

   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()`` :

.. code:: sql

   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”:

.. code:: sql

   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 :ref:`Agents reference <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
:ref:`Delegation <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.

.. code:: sql

   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 :ref:`aidb.create_agent <aidb.create_agent>`  :

.. csv-table::
  :header: Parameter,What it caps
  :widths: 10,30
  :align: left
  :class: longtable

  `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`` :

.. csv-table::
  :header: Strategy,Behavior
  :widths: 10,30
  :align: left
  :class: longtable

  `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 :ref:`Debug mode <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).
