diff --git a/README.md b/README.md
index 7bd2a08..00e33f8 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,10 @@ The CLI passes both values to the Select AI SDK as `wallet_location` and
The server accepts both A2A 1.x and the A2A v0.3 JSON-RPC streaming protocol
for compatibility with Gemini Enterprise.
+See the [A2A user guide](doc/source/user_guide/a2a.rst) for the dynamic
+gateway, A2UI connection flow, persistent task state, task polling and
+cancellation, wallet configuration, and Google Cloud deployment modes.
+
Generate the A2A v0.3 Agent Card to paste into Gemini Enterprise after the
service has a public URL:
diff --git a/doc/source/image/a2a_architecture.png b/doc/source/image/a2a_architecture.png
new file mode 100644
index 0000000..bc9da1a
Binary files /dev/null and b/doc/source/image/a2a_architecture.png differ
diff --git a/doc/source/image/a2a_architecture.svg b/doc/source/image/a2a_architecture.svg
new file mode 100644
index 0000000..c988b00
--- /dev/null
+++ b/doc/source/image/a2a_architecture.svg
@@ -0,0 +1,131 @@
+
diff --git a/doc/source/image/conversation_object_model.png b/doc/source/image/conversation_object_model.png
new file mode 100644
index 0000000..8d45cfe
Binary files /dev/null and b/doc/source/image/conversation_object_model.png differ
diff --git a/doc/source/image/conversation_object_model.svg b/doc/source/image/conversation_object_model.svg
new file mode 100644
index 0000000..fb77f07
--- /dev/null
+++ b/doc/source/image/conversation_object_model.svg
@@ -0,0 +1,99 @@
+
diff --git a/doc/source/image/profile_object_model.png b/doc/source/image/profile_object_model.png
new file mode 100644
index 0000000..6caa418
Binary files /dev/null and b/doc/source/image/profile_object_model.png differ
diff --git a/doc/source/image/profile_object_model.svg b/doc/source/image/profile_object_model.svg
new file mode 100644
index 0000000..345a552
--- /dev/null
+++ b/doc/source/image/profile_object_model.svg
@@ -0,0 +1,107 @@
+
diff --git a/doc/source/image/vector_index_object_model.png b/doc/source/image/vector_index_object_model.png
new file mode 100644
index 0000000..7090fe2
Binary files /dev/null and b/doc/source/image/vector_index_object_model.png differ
diff --git a/doc/source/image/vector_index_object_model.svg b/doc/source/image/vector_index_object_model.svg
new file mode 100644
index 0000000..a9e47eb
--- /dev/null
+++ b/doc/source/image/vector_index_object_model.svg
@@ -0,0 +1,101 @@
+
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 0a2dc6a..767ab0f 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -59,6 +59,15 @@ Credential
user_guide/credential.rst
+Sharing and ownership
+=====================
+
+.. toctree::
+ :numbered:
+ :maxdepth: 3
+
+ user_guide/sharing.rst
+
Profile Attributes
==================
@@ -143,6 +152,15 @@ Async AI Agent
user_guide/async_agent.rst
+A2A Integration
+===============
+
+.. toctree::
+ :numbered:
+ :maxdepth: 3
+
+ user_guide/a2a.rst
+
Command Line Interface
======================
diff --git a/doc/source/user_guide/a2a.rst b/doc/source/user_guide/a2a.rst
new file mode 100644
index 0000000..f6a8eb5
--- /dev/null
+++ b/doc/source/user_guide/a2a.rst
@@ -0,0 +1,768 @@
+.. _a2a:
+
+********************
+Agent2Agent (A2A)
+********************
+
+Select AI for Python can expose an Oracle Database AI Agent Team through the
+`Agent2Agent (A2A) protocol `__. A2A clients can
+discover the team from an Agent Card, submit database prompts, and retrieve
+task results through the same JSON-RPC endpoint.
+
+There are two deployment modes:
+
+* **Standalone A2A server**: one server owns a configured Oracle connection
+ pool and one configured Select AI team. This mode supports streaming and is
+ suitable when the service owner controls the database identity.
+* **Dynamic A2A gateway**: a public gateway asks the client for a database DSN,
+ username, password, and team name through an A2UI form. It opens a temporary
+ isolated worker session for that selection. This mode supports task polling,
+ but does not advertise or implement streaming.
+
+.. only:: html
+
+ .. figure:: /image/a2a_architecture.svg
+ :alt: Architecture comparison of the standalone A2A server and dynamic A2A gateway
+ :width: 100%
+
+ Select AI A2A deployment modes. The standalone server fixes the database
+ and team at startup; the gateway selects them for each temporary session.
+
+.. only:: latex
+
+ .. figure:: /image/a2a_architecture.png
+ :alt: Architecture comparison of the standalone A2A server and dynamic A2A gateway
+ :width: 100%
+
+ Select AI A2A deployment modes. The standalone server fixes the database
+ and team at startup; the gateway selects them for each temporary session.
+
+CLI-based deployment on any cloud
+==================================
+
+The A2A runtimes are provided as ordinary Select AI CLI commands, so they are
+not tied to a particular cloud provider. Run the commands on a local machine,
+a virtual machine, a container platform, or a Kubernetes service in any cloud
+that can reach the required Oracle Database and, for dynamic deployments, the
+Consul service:
+
+* ``select-ai a2a serve`` runs the standalone server.
+* ``select-ai a2a gateway`` runs the public dynamic gateway.
+* ``select-ai a2a worker`` runs a database-bearing dynamic worker.
+
+The commands can be packaged into the platform's preferred container or
+process deployment. The Google Cloud scripts documented below are convenience
+automation for a Cloud Run/GKE deployment; they are not required to use the
+A2A CLI commands on another cloud or on self-managed infrastructure.
+
+The public routes are the same in both modes:
+
+``/.well-known/agent-card.json``
+ Agent Card discovery endpoint.
+
+``/a2a/jsonrpc/``
+ A2A JSON-RPC endpoint. The endpoint accepts the A2A 1.0 method names and
+ the A2A v0.3 compatibility method names.
+
+.. list-table:: Deployment mode comparison
+ :header-rows: 1
+ :widths: 23 38 39
+ :align: left
+
+ * - Concern
+ - Standalone server
+ - Dynamic gateway
+ * - Database and team
+ - Fixed in the server configuration.
+ - Selected by the client for each session.
+ * - Public process
+ - ``select-ai a2a serve``
+ - ``select-ai a2a gateway``
+ * - Session process
+ - The server's shared asynchronous connection pool.
+ - A worker child process and connection pool per active session.
+ * - State
+ - Oracle-backed tasks, A2A contexts, and conversations.
+ - The same Oracle-backed state, with Consul routing metadata.
+ * - Streaming
+ - Advertised and supported.
+ - Not advertised and rejected; use task polling.
+ * - Database authentication
+ - DSN/user/password, with optional Oracle wallet.
+ - DSN/user/password submitted through A2UI; wallet connections are not
+ currently supported by this session path.
+
+Installation and prerequisites
+==============================
+
+Install the A2A dependencies before running the server, gateway, or worker:
+
+.. code-block:: bash
+
+ python -m pip install 'select_ai[a2a]'
+
+All modes require:
+
+* an Oracle Database where the Select AI Agent Team is installed;
+* a database user with the privileges required by Select AI and the team; and
+* network access from the process to Oracle Database.
+
+The standalone server needs one database identity at startup. The gateway does
+not need a database identity at startup, but each submitted A2UI connection
+form must contain a valid DSN, username, password, and team name.
+
+Protocol compatibility
+======================
+
+The server and gateway use the same JSON-RPC URL for A2A 1.0 and v0.3. The
+protocol adapter translates the v0.3 method names and Agent Card shape for
+clients that have not migrated to A2A 1.0.
+
+.. list-table:: Common A2A operations
+ :header-rows: 1
+ :widths: 28 34 38
+ :align: left
+
+ * - Operation
+ - A2A 1.0
+ - A2A v0.3 compatibility name
+ * - Send a message
+ - ``SendMessage``
+ - ``message/send``
+ * - Stream a message
+ - ``SendStreamingMessage``
+ - ``message/stream``
+ * - Get a task
+ - ``GetTask``
+ - ``tasks/get``
+ * - List tasks
+ - ``ListTasks``
+ - ``tasks/list``
+ * - Cancel a task
+ - ``CancelTask``
+ - ``tasks/cancel``
+
+For A2A 1.0 clients, use ``A2A-Version: 1.0`` when the client library requires
+an explicit version header. A v0.3 client uses the compatibility names. The
+``select-ai a2a agent-card`` command prints a v0.3 Agent Card that can be
+registered with Gemini Enterprise.
+
+Standalone A2A server
+=====================
+
+The standalone server exposes one database AI Agent Team. The database user,
+password, DSN, and optional wallet are configured when the process starts. The
+team name is required:
+
+.. code-block:: bash
+
+ export SELECT_AI_USER=select_ai_user
+ export SELECT_AI_PASSWORD='database-password'
+ export SELECT_AI_DB_CONNECT_STRING='db2025adb_medium'
+
+ select-ai a2a serve \
+ --team ORACLE_AI_DATABASE_AGENT \
+ --host 0.0.0.0 \
+ --port 8000 \
+ --public-url https://a2a.example.com
+
+The connection values can also be passed with ``--user``, ``--password``, and
+``--dsn``. If no password is supplied, the CLI prompts for it. ``--public-url``
+is the externally reachable base URL placed into the Agent Card; set it when a
+proxy or load balancer sits in front of the process.
+
+The server prints the discovery URL when it starts:
+
+.. code-block:: text
+
+ A2A Agent Card: https://a2a.example.com/.well-known/agent-card.json
+ INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
+
+The exact Uvicorn startup lines vary by version and configuration.
+
+Agent Card discovery
+~~~~~~~~~~~~~~~~~~~~
+
+Fetch the card after starting the server:
+
+.. code-block:: bash
+
+ curl -sS https://a2a.example.com/.well-known/agent-card.json | jq
+
+The discovery response is the A2A v0.3-compatible representation used by
+clients such as Gemini Enterprise. It identifies the JSON-RPC endpoint and
+advertises streaming. The server's internal A2A 1.0 card declares both a
+JSON-RPC 1.0 interface and a JSON-RPC v0.3 interface at that same endpoint.
+
+When a service has a public URL, the portable v0.3 card can also be printed
+without starting the server:
+
+.. code-block:: bash
+
+ select-ai a2a agent-card \
+ --team ORACLE_AI_DATABASE_AGENT \
+ --public-url https://a2a.example.com
+
+This command prints JSON similar to:
+
+.. code-block:: json
+
+ {
+ "protocolVersion": "0.3",
+ "name": "ORACLE_AI_DATABASE_AGENT",
+ "url": "https://a2a.example.com/a2a/jsonrpc/",
+ "capabilities": {"streaming": true},
+ "defaultInputModes": ["text/plain"],
+ "defaultOutputModes": ["text/plain"]
+ }
+
+The package version, description, and skill list are also included in the
+actual output.
+
+Tasks, polling, streaming, and cancellation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The standalone server supports these execution patterns:
+
+* **Blocking**: send a message without a non-blocking option. The request
+ waits until the task reaches a terminal state and returns the completed
+ task.
+* **Non-blocking and polling**: send a v0.3 request with
+ ``configuration.blocking: false`` or an A2A 1.0 request with
+ ``configuration.returnImmediately: true``. Poll ``tasks/get`` or
+ ``GetTask`` until the task reaches ``completed``, ``failed``, ``canceled``,
+ or ``rejected``.
+* **Streaming**: use ``message/stream`` or ``SendStreamingMessage``. The Agent
+ Card advertises ``streaming: true`` and the endpoint returns the protocol's
+ streaming events over the HTTP streaming response.
+* **Cancellation**: call ``tasks/cancel`` or ``CancelTask`` with the task ID.
+ The server updates the task through the A2A task handler and returns its
+ canceled state when cancellation succeeds.
+
+The repository includes the
+`blocking_task.py sample `__
+and the
+`task_poll.py sample `__:
+
+.. literalinclude:: ../../../samples/a2a/blocking_task.py
+ :language: python
+ :lines: 8-
+
+The blocking sample omits ``configuration``. Representative output is:
+
+.. code-block:: text
+
+ Task 7e1...: completed
+ {
+ "id": "7e1...",
+ "status": {"state": "completed", "timestamp": "..."},
+ "artifacts": [{"name": "database-agent-result", "parts": ["..."]}]
+ }
+
+The task ID, timestamp, and database answer vary for each run.
+
+.. literalinclude:: ../../../samples/a2a/task_poll.py
+ :language: python
+ :lines: 8-
+
+The polling sample sends a non-blocking request and then calls ``tasks/get``:
+
+.. code-block:: text
+
+ Task 42b...: submitted
+ Task 42b...: working
+ Task 42b...: completed
+ {
+ "id": "42b...",
+ "status": {"state": "completed", "timestamp": "..."},
+ "artifacts": [{"name": "database-agent-result", "parts": ["..."]}]
+ }
+
+.. _persistent-oracle-backed-state:
+
+Persistent Oracle-backed state
+==============================
+
+Both A2A deployment modes use Oracle implementations of the A2A task and
+context stores. In standalone mode, the server initializes these stores when
+the application starts. In dynamic gateway mode, ``select-ai a2a gateway``
+does not connect to Oracle itself; the ``select-ai a2a worker`` command starts
+the internal worker, and each connected worker session initializes the stores
+after it opens its supplied database connection. On first initialization, the
+stores create these tables if they do not already exist:
+
+``SELECT_AI_A2A_TASKS``
+ Stores the task ID, context ID, serialized task JSON, owner, and update
+ timestamp. It supports task retrieval, filtering, listing, pagination, and
+ deletion.
+
+``SELECT_AI_A2A_CONTEXTS``
+ Maps an A2A context and owner to an Oracle conversation ID.
+
+When a request starts a new context, Select AI creates an
+``AsyncConversation`` and passes its ID to ``AsyncTeam.run``. Later messages in
+the same A2A context reuse that conversation, so the team can use the
+conversation history. In standalone mode, task and context ownership is
+resolved from the A2A request context; an absent owner is stored as
+``anonymous``. In gateway mode, the worker scopes the rows to the temporary
+session ID, which is the A2A context ID used to route requests back to that
+worker session.
+
+The tables hold task and context metadata, while the conversation prompts and
+responses remain in the regular Select AI conversation storage. This means a
+server or worker process restart does not discard task records, context
+mappings, or conversation history, provided the same database and owner scope
+are used. Gateway routing records and worker child processes are temporary:
+after a worker session expires or is lost, submit the connection form again so
+the gateway can recreate the session and route requests to the durable Oracle
+state. The gateway's temporary connection-form task itself is not stored in
+Oracle; durable A2A tasks are created by the worker after the database session
+is ready.
+
+Wallet-based database connections
+=================================
+
+Wallet-based connections apply to the standalone ``select-ai a2a serve``
+path. The standalone server passes wallet settings to the Select AI
+asynchronous connection pool. Use the unzipped wallet directory with
+``SELECT_AI_WALLET_LOCATION`` or ``--wallet-location`` and provide the wallet
+password with ``SELECT_AI_WALLET_PASSWORD`` or ``--wallet-password``:
+
+.. code-block:: bash
+
+ export SELECT_AI_WALLET_LOCATION=/secure/wallet/Wallet_database
+ export SELECT_AI_WALLET_PASSWORD='wallet-password'
+ export SELECT_AI_DB_CONNECT_STRING=db2025adb_medium
+
+ select-ai a2a serve \
+ --team ORACLE_AI_DATABASE_AGENT \
+ --user SELECT_AI_USER \
+ --dsn db2025adb_medium
+
+``--wallet-location`` is used as both the wallet location and the Oracle
+configuration directory. Keep wallet files and passwords outside the source
+tree. The
+`standalone deployment script `__
+can upload a wallet archive
+to Secret Manager and expand it into ephemeral Cloud Run storage; see
+`Standalone Google Cloud deployment`_ below.
+
+Dynamic A2A gateway
+===================
+
+The dynamic mode separates the public A2A protocol endpoint from the
+database-bearing runtime. The gateway starts without database credentials. A
+worker registers with Consul and waits for the gateway to assign sessions. The
+worker's connected sessions use the persistent Oracle-backed task, context, and
+conversation state described in :ref:`Persistent Oracle-backed state
+`; Consul stores only the temporary routing
+metadata.
+
+Consul provides the gateway's service-discovery and lightweight routing
+control plane. Workers register their address and health check with Consul;
+the gateway asks Consul for a passing worker when a session is opened. The
+gateway then stores the session-to-worker and task-to-session routes in Consul
+so later requests go directly to the worker that owns the session. Consul does
+not store database credentials, task payloads, or conversation history.
+
+For local development, install the Consul command-line binary before starting
+the local agent. HashiCorp provides package-manager and precompiled-binary
+instructions in the `Consul installation guide
+`__.
+After installation, verify that the binary is on ``PATH``:
+
+.. code-block:: bash
+
+ consul version
+
+The ``-dev`` agent below is intentionally for local development only. It uses
+an in-memory, single-node Consul server bound to loopback. For the Google
+Cloud deployment, do not install or run a local agent: the deployment creates
+Consul in GKE from the
+`Consul manifest `__.
+
+Start the three local components in separate terminals:
+
+.. code-block:: bash
+
+ # Terminal 1: Consul
+ consul agent -dev -bind=127.0.0.1 -client=127.0.0.1
+
+ # Terminal 2: worker
+ CONSUL_HTTP_URL=http://127.0.0.1:8500 \
+ WORKER_ID=local-worker \
+ WORKER_ADDRESS=127.0.0.1 \
+ WORKER_PORT=8081 \
+ select-ai a2a worker \
+ --host 127.0.0.1 \
+ --port 8081
+
+ # Terminal 3: public gateway
+ select-ai a2a gateway \
+ --host 127.0.0.1 \
+ --port 8000 \
+ --agent-url http://127.0.0.1:8000 \
+ --consul-url http://127.0.0.1:8500
+
+The worker uses ``CONSUL_HTTP_URL`` (default ``http://consul:8500``),
+``WORKER_ID``, ``WORKER_ADDRESS``, ``WORKER_PORT``, and the optional
+``WORKER_ENDPOINT`` environment variables when registering with Consul. The
+gateway uses ``AGENT_URL``, ``CONSUL_HTTP_URL``, ``WORKER_SERVICE`` (default
+``select-ai-worker``), and ``SESSION_TTL_SECONDS``. The command-line options
+override the corresponding environment variables.
+
+The worker must be able to resolve the submitted DSN. For a TNS alias, set
+``TNS_ADMIN`` in the worker terminal before starting it. The gateway session
+path accepts a DSN, username, and password; it does not currently accept an
+Oracle wallet.
+
+The gateway's Agent Card advertises ``streaming: false``. It supports blocking
+tasks, non-blocking tasks, task retrieval/listing, and cancellation by
+forwarding the request to the worker session. Streaming and push-notification
+operations are rejected because the gateway does not proxy a live stream.
+
+A2UI database-connection form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The first ``message/send`` for a context returns a temporary task containing a
+database connection form. The form is an A2UI v0.9 data artifact with the
+``application/json+a2ui`` MIME type. It contains these fields:
+
+.. list-table:: Dynamic connection form
+ :header-rows: 1
+ :widths: 25 75
+ :align: left
+
+ * - Field
+ - Purpose
+ * - ``dsn``
+ - Oracle connect descriptor, simplified connect string, or TNS alias
+ available to the worker.
+ * - ``username``
+ - Database user for this session.
+ * - ``password``
+ - Database password. The form renders this field as obscured input.
+ * - ``team_name``
+ - Select AI Agent Team to run in this session.
+ * - ``submit_database_connection``
+ - A2UI action that submits the four values to the gateway.
+
+After the action is submitted, the gateway selects a healthy worker through
+Consul and opens a session. The worker starts a child process, calls
+``select_ai.async_connect`` with the supplied DSN/user/password, validates the
+team, initializes the Oracle task and context stores, and returns a
+``database-session`` artifact. The client can then send ordinary database
+prompts with the same A2A context ID.
+
+Credentials are used to open the session and are not written to Consul. Consul
+stores only the worker endpoint and expiration for the session, plus task-to-
+session routing metadata. The credentials remain in the worker child process
+for the lifetime of that session, so use TLS for client-to-gateway traffic and
+follow the security policies for any client that renders the form.
+
+The gateway samples perform this handshake automatically. They use the
+repository-local helper module
+`samples/a2a/gateway/_common.py `__;
+``_common``
+is not a package that users install with ``pip``. Running the samples from the
+repository root as shown below makes that helper available automatically.
+
+The helper functions are:
+
+``call(method, params)``
+ Sends one A2A v0.3 JSON-RPC request to the configured gateway endpoint.
+
+``connect(prompt)``
+ Sends the initial prompt, reads the A2UI connection form, submits the
+ ``SELECT_AI_DB_CONNECT_STRING``, ``SELECT_AI_USER``,
+ ``SELECT_AI_PASSWORD``, and ``SELECT_AI_A2A_TEAM`` values, and returns the
+ A2A context ID for the connected session.
+
+``send_prompt(prompt, context_id, blocking=None)``
+ Sends a database prompt in an existing gateway context. Passing
+ ``blocking=False`` adds the non-blocking request option.
+
+``print_task_summary(task)``
+ Prints the task state, result artifact name, and text parts without dumping
+ the connection-form details.
+
+If you copy a gateway sample into another directory, copy the
+`_common.py helper `__
+with it, or replace these helpers with an A2A client implementation of your
+own.
+
+The complete gateway setup is also documented in the
+`gateway sample README `__.
+The examples below are the
+`blocking_task.py sample `__
+and the
+`task_poll.py sample `__.
+
+.. literalinclude:: ../../../samples/a2a/gateway/blocking_task.py
+ :language: python
+ :lines: 8-
+
+Set the database values before running the sample:
+
+.. code-block:: bash
+
+ export SELECT_AI_DB_CONNECT_STRING='database-dsn'
+ export SELECT_AI_USER='database-user'
+ export SELECT_AI_PASSWORD='database-password'
+ export SELECT_AI_A2A_TEAM='ORACLE_AI_DATABASE_AGENT'
+
+Run it with:
+
+.. code-block:: bash
+
+ python samples/a2a/gateway/blocking_task.py
+
+Representative output is:
+
+.. code-block:: text
+
+ Task 15c...: completed
+ Artifact: database-agent-result
+ The database contains ...
+
+The gateway polling sample uses the same connection-form handshake, then
+passes ``configuration.blocking: false`` and polls ``tasks/get``:
+
+.. literalinclude:: ../../../samples/a2a/gateway/task_poll.py
+ :language: python
+ :lines: 8-
+
+.. code-block:: text
+
+ Task 91a...: submitted
+ Task 91a...: working
+ Task 91a...: completed
+ Artifact: database-agent-result
+ The database contains ...
+
+Session isolation, routing, and cleanup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Each successful connection action creates one worker session identified by the
+A2A context ID. The worker owns an operating-system child process for that
+session. The child process owns its Select AI asynchronous connection, A2A
+handler, task store, context store, and database conversation access. Requests
+for different sessions therefore do not share database credentials or a
+connection pool.
+
+The default session lifetime is 900 seconds (15 minutes). Change it with
+``--session-ttl-seconds`` on the worker and gateway, or with the gateway's
+``SESSION_TTL_SECONDS`` environment variable. The worker reaper checks for
+expired or dead child processes and terminates them. The gateway also checks
+the expiry stored in Consul. When a route or child session is no longer
+available, the gateway returns an A2UI connection form so the client can
+reconnect rather than silently sending a request to a different database.
+
+Consul is used for two kinds of routing metadata:
+
+* the worker service registration and health TTL, which let the gateway select
+ a passing worker; and
+* ``select-ai/sessions/`` and ``select-ai/tasks/`` key-value records, which
+ route a context or task back to the worker that owns it.
+
+The task and conversation contents are not stored in Consul. They are stored
+in Oracle by the selected worker. A worker registers a health check and sends
+heartbeats; it deregisters on shutdown. The gateway selects healthy workers
+round-robin when opening new sessions.
+
+Gateway-to-worker mTLS
+~~~~~~~~~~~~~~~~~~~~~~
+
+Local development uses ordinary HTTP. For an internal deployment, the gateway
+can authenticate workers and workers can require a gateway client certificate.
+All three gateway files are required together:
+
+.. code-block:: bash
+
+ select-ai a2a gateway \
+ --agent-url https://gateway.example.com \
+ --worker-tls-ca-file /run/secrets/worker-ca.pem \
+ --worker-tls-cert-file /run/secrets/gateway-client.crt \
+ --worker-tls-key-file /run/secrets/gateway-client.key
+
+Start the worker with the matching server certificate, key, and CA:
+
+.. code-block:: bash
+
+ select-ai a2a worker \
+ --tls-cert-file /run/secrets/worker.crt \
+ --tls-key-file /run/secrets/worker.key \
+ --tls-ca-file /run/secrets/gateway-client-ca.pem
+
+The worker must register an HTTPS endpoint through ``WORKER_ENDPOINT`` when
+mTLS is enabled. The gateway verifies the worker certificate with the CA and
+presents its client certificate. This mTLS option protects only the
+gateway-to-worker HTTP hop. It does not add wallet-based mTLS to the gateway's
+database connection path.
+
+Google Cloud deployment
+=======================
+
+The repository contains complete deployment workflows under the
+`gcloud directory `__.
+The
+two scripts deploy different topologies, so choose the directory that matches
+the desired runtime model.
+
+Standalone Google Cloud deployment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`gcloud/standalone/deploy.sh `__
+deploys one private Cloud Run service for one
+database and team:
+
+.. code-block:: bash
+
+ gcloud/standalone/deploy.sh \
+ --project PROJECT_ID \
+ --a2a-team ORACLE_AI_DATABASE_AGENT \
+ --build
+
+The script creates or reuses an Artifact Registry repository, runtime service
+account, Secret Manager secrets, and Cloud Run service. On the first run it
+prompts for the database user, password, and connect descriptor. It injects
+them into the service as ``SELECT_AI_USER``, ``SELECT_AI_PASSWORD``, and
+``SELECT_AI_DB_CONNECT_STRING``. The service is private; the script grants
+``run.routes.invoke`` to the active deployment identity and the Gemini
+Enterprise Discovery Engine service agent.
+
+Use ``--build`` when the current checkout should become a new container image.
+The script submits
+`gcloud/standalone/cloudbuild.yaml `__
+to Cloud Build, which
+builds ``docker/Dockerfile`` from the repository root and pushes the image to
+Artifact Registry. Without ``--build``, a later invocation reuses the image
+already deployed and only updates Cloud Run configuration or secrets.
+
+Important standalone options include:
+
+* ``--service``: Cloud Run service name; use a different service for each
+ fixed team/database deployment.
+* ``--a2a-team``: team installed in Oracle Database.
+* ``--pool-max-size``: maximum Oracle connections per Cloud Run instance.
+* ``--max-instances``: Cloud Run instance limit.
+* ``--wallet-archive``: upload or replace an Autonomous Database wallet ZIP.
+* ``--rotate-db-credentials``: prompt for and rotate the database secrets.
+
+For a wallet deployment, ``--wallet-archive`` stores the ZIP and wallet
+password in service-specific Secret Manager secrets. Cloud Run mounts the ZIP
+read-only; the launcher expands it into ephemeral ``/tmp`` storage, verifies
+the wallet, and sets ``SELECT_AI_WALLET_LOCATION`` before starting the A2A
+server. Do not commit wallet files or put them in the container image.
+
+The standalone script prints the deployed Agent Card JSON at the end. The
+service is ready for a client when the printed card points to the final Cloud
+Run URL and the client can invoke the private service.
+
+Dynamic gateway Google Cloud deployment
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+`gcloud/gateway/deploy.sh `__
+deploys the gateway topology: Cloud Run for the
+public gateway, GKE Autopilot for worker replicas, and Consul for discovery and
+routing.
+
+.. code-block:: bash
+
+ gcloud/gateway/deploy.sh \
+ --project PROJECT_ID \
+ --region us-central1 \
+ --worker-replicas 3
+
+The script and
+`gcloud/gateway/cloudbuild.yaml `__
+perform these steps:
+
+1. Enable the required Google Cloud APIs and create or reuse Artifact Registry
+ and a GKE Autopilot cluster.
+2. Configure additive VPC DNS so StatefulSet worker names resolve to current
+ Pod IPs after a worker is recreated.
+3. Deploy the gateway namespace, internal Consul service, and worker service.
+4. Build and publish the image with Cloud Build.
+5. Deploy the requested worker replicas with ``select-ai a2a worker`` and the
+ selected session TTL.
+6. Deploy Cloud Run with ``select-ai a2a gateway`` and set ``AGENT_URL`` to the
+ final Cloud Run URL.
+
+The Cloud Run gateway uses direct VPC egress to reach the internal Consul
+load-balancer address and worker endpoints. The GKE worker service is headless
+service discovery, not a public load balancer. Consul selects a healthy worker
+when a session opens; the route is then pinned to that worker.
+
+Important gateway options include:
+
+* ``--cluster`` and ``--gke-dns-domain``: GKE Autopilot cluster and immutable
+ additive DNS domain.
+* ``--network`` and ``--subnet``: VPC path used by Cloud Run to reach the
+ internal worker infrastructure.
+* ``--worker-replicas``: number of worker runtimes available for new sessions.
+* ``--session-ttl-seconds``: lifetime of temporary database sessions.
+* ``--enable-worker-mtls``: create and use gateway-to-worker certificates.
+* ``--rotate-worker-mtls``: replace the existing test certificates and restart
+ the worker workload.
+
+The default gateway deployment uses private-VPC HTTP between Cloud Run and
+GKE. With ``--enable-worker-mtls``, the script creates short-lived test PKI
+material in Secret Manager, creates the Kubernetes TLS secrets, deploys the
+worker StatefulSet variant, and mounts the gateway client certificate into
+Cloud Run. The worker certificate uses the GKE StatefulSet DNS name, so the
+``--gke-dns-domain`` value must remain consistent with the cluster.
+
+The gateway deployment does not upload an Oracle wallet because the dynamic
+worker session path currently accepts only DSN/user/password. If database mTLS
+is required, use the standalone deployment or provide a separate database
+connection mechanism to the worker implementation.
+
+The deployment files are intended to be read together:
+
+.. list-table:: Google Cloud A2A deployment files
+ :header-rows: 1
+ :widths: 32 68
+ :align: left
+
+ * - File
+ - Role
+ * - `gcloud/standalone/deploy.sh `__
+ - Creates and updates one private Cloud Run standalone server.
+ * - `gcloud/standalone/cloudbuild.yaml `__
+ - Builds and publishes the standalone container image.
+ * - `gcloud/standalone/README.md `__
+ - Documents IAM, secrets, wallet archives, and update behavior.
+ * - `gcloud/gateway/deploy.sh `__
+ - Creates or reuses the gateway GKE/Cloud Run topology and supplies build
+ substitutions.
+ * - `gcloud/gateway/cloudbuild.yaml `__
+ - Builds the image, deploys Consul and workers, and deploys Cloud Run.
+ * - `gcloud/gateway/gke manifests `__
+ - Namespace, Consul, headless worker service, and HTTP or mTLS worker
+ workloads.
+ * - `gcloud/gateway/README.md `__
+ - Explains the topology, DNS, mTLS test mode, and operational details.
+
+Troubleshooting and security notes
+==================================
+
+* If the Agent Card advertises the wrong URL, set ``--public-url`` for the
+ standalone server or ``AGENT_URL`` for the gateway. The URL must be the
+ client-visible base URL, not an internal container address.
+* If the gateway returns the connection form repeatedly, check that the worker
+ is passing in Consul, that the worker can resolve and connect to Oracle, and
+ that the session TTL has not expired.
+* If a task can no longer be retrieved after a worker restart, submit the A2UI
+ form again. Consul routes are intentionally tied to the worker session, even
+ though task data is durable in Oracle.
+* Do not put database passwords in source files, container images, Consul KV,
+ or deployment logs. Use Secret Manager for fixed standalone credentials and
+ protect the A2UI/gateway path with HTTPS.
+* Gateway mTLS and Oracle wallet mTLS are different controls. Gateway mTLS
+ authenticates the internal HTTP peer; an Oracle wallet authenticates the
+ database connection and is currently available only to the standalone
+ server path.
+
+See :ref:`Command Line Interface ` for the common CLI connection options
+and :ref:`Connection ` for Select AI database connection setup.
diff --git a/doc/source/user_guide/agent.rst b/doc/source/user_guide/agent.rst
index d86e366..309d43d 100644
--- a/doc/source/user_guide/agent.rst
+++ b/doc/source/user_guide/agent.rst
@@ -182,6 +182,39 @@ output::
.. latex:clearpage::
+Inspect and run a tool
+++++++++++++++++++++++
+
+Use ``Tool.describe_tool()`` to return JSON metadata for a tool, including its
+function arguments. Use ``Tool.run_tool(input)`` to invoke a tool directly with
+an input payload. Direct execution is useful for testing a tool independently
+of a team workflow.
+
+.. code-block:: python
+
+ tool = Tool.fetch("MOVIE_SQL_TOOL")
+ print(tool.describe_tool())
+ print(tool.run_tool('{"query": "How many movies are there?"}'))
+
+The complete example creates a temporary PL/SQL tool, describes it, invokes it,
+and removes the temporary database objects:
+
+.. literalinclude:: ../../../samples/agent/tool_run_describe.py
+ :language: python
+ :lines: 14-
+
+The generated tool name and calculated age vary between runs. Representative
+output is:
+
+output::
+
+ Tool description:
+ {"tool_name": "SAMPLE_AGE_TOOL_", ...}
+ Tool result:
+
+
+.. latex:clearpage::
+
********
``Task``
********
@@ -319,11 +352,80 @@ For example:
.. autoclass:: select_ai.agent.TeamAttributes
:members:
+Supervised teams
+++++++++++++++++
+
+A supervised team uses a dedicated agent to coordinate the other agents in the
+team. Set ``supervisor=True`` on that agent's ``AgentAttributes`` and pass the
+agent name in ``TeamAttributes.supervisor_agent``. The worker agent and task
+assignments remain in the team's ``agents`` list.
+
+``supervisor_task`` is created by the database when the team is created. Do not
+set it when constructing ``TeamAttributes``. Fetch the team after creation to
+read the generated value from ``fetched.attributes.supervisor_task``.
+
+.. code-block:: python
+
+ supervisor = Agent(
+ agent_name="MOVIE_SUPERVISOR",
+ attributes=AgentAttributes(
+ profile_name="oci_ai_profile",
+ role="You supervise and coordinate the team.",
+ supervisor=True,
+ ),
+ )
+
+ team = Team(
+ team_name="MOVIE_AGENT_TEAM",
+ attributes=TeamAttributes(
+ agents=[
+ {"name": "MOVIE_ANALYST", "task": "ANALYZE_MOVIE_TASK"}
+ ],
+ process="sequential",
+ supervisor_agent=supervisor.agent_name,
+ ),
+ )
+
+The complete example creates a supervised team, runs a prompt through the
+supervisor workflow, and inspects the generated supervisor task and team
+metadata. ``Team.run()`` starts the team workflow; the configured supervisor
+agent coordinates the worker agent as part of that call.
+
+.. literalinclude:: ../../../samples/agent/team_supervisor_inspect.py
+ :language: python
+ :lines: 14-
+
+The generated names and returned JSON metadata vary between runs. Representative
+output is:
+
+output::
+
+ Team response:
+ Supervisor agent: SAMPLE_SUPERVISOR_
+ Supervisor task:
+ Team description:
+ Team tools:
+
.. latex:clearpage::
.. autoclass:: select_ai.agent.Team
:members:
+Share a team
+++++++++++++
+
+Grant or revoke access for a database user or role with ``Team`` methods. Run
+these methods as the team owner:
+
+.. code-block:: python
+
+ team = Team.fetch("MOVIE_AGENT_TEAM")
+ team.grant_access("APP_USER")
+ team.revoke_access("APP_USER")
+
+Team fetch and list operations use the connected user's agent-team views and do
+not currently accept an ``owner`` argument.
+
.. latex:clearpage::
Run Team
@@ -425,6 +527,21 @@ storage, ``Team.export_team()`` writes the specification to the location and
returns ``None``. When importing from object storage, pass the same credential
and location instead of ``specification``.
+Inspect a team
+++++++++++++++
+
+``Team.describe_team()`` returns JSON metadata and the aggregated skills for a
+team. ``Team.list_tools()`` returns JSON metadata for the tools available to
+that team.
+
+.. code-block:: python
+
+ team = Team.fetch("MOVIE_AGENT_TEAM")
+ print(team.describe_team())
+ print(team.list_tools())
+
+The supervised-team sample also demonstrates both inspection methods.
+
Lifecycle helpers
+++++++++++++++++
@@ -446,6 +563,39 @@ operations.
.. latex:clearpage::
+Object definitions
+******************
+
+Use ``get_definition(object_type, object_name)`` to retrieve the canonical
+PL/SQL block for recreating an AI ``AGENT``, ``TASK``, ``TOOL``, or ``TEAM``.
+The function returns ``None`` when the database does not return a definition.
+
+.. code-block:: python
+
+ from select_ai.agent import get_definition
+
+ definition = get_definition("TASK", "ANALYZE_MOVIE_TASK")
+ print(definition)
+
+The asynchronous equivalent is ``async_get_definition``. See the sync and
+async definition samples for a complete create, inspect, and cleanup flow.
+
+.. literalinclude:: ../../../samples/agent/get_definition.py
+ :language: python
+ :lines: 14-
+
+The task name and exact PL/SQL formatting vary between runs and database
+versions. Representative output is:
+
+output::
+
+ BEGIN
+ DBMS_CLOUD_AI_AGENT.CREATE_TASK(...);
+ END;
+ /
+
+.. latex:clearpage::
+
*************
Agent history
*************
@@ -482,6 +632,22 @@ to retrieve the associated task and tool history.
.. autoclass:: select_ai.agent.ToolHistory
:members:
+The complete sample prints the latest team execution and the task and tool
+events associated with that execution:
+
+.. literalinclude:: ../../../samples/agent/history_list.py
+ :language: python
+ :lines: 14-
+
+History depends on an existing execution for the configured team. Representative
+output is:
+
+output::
+
+ TeamHistoryEvent(team_exec_id='', team_name='ORACLE_AI_DATABASE_AGENT', state='', ...)
+ TaskHistoryEvent(team_exec_id='', task_name='', state='', ...)
+ ToolHistoryEvent(invocation_id=, team_exec_id='', tool_name='', ...)
+
.. latex:clearpage::
*****************
diff --git a/doc/source/user_guide/async_agent.rst b/doc/source/user_guide/async_agent.rst
index 5f48f42..06377d5 100644
--- a/doc/source/user_guide/async_agent.rst
+++ b/doc/source/user_guide/async_agent.rst
@@ -8,6 +8,7 @@ use ``asyncio`` and ``select_ai.async_connect()`` or
The history API follows the same pattern. ``AsyncTeamHistory``,
``AsyncTaskHistory``, and ``AsyncToolHistory`` query only the current user's
history views and yield typed events newest first.
+See :ref:`async-agent-history` for the complete API reference and sample.
.. code-block:: python
@@ -150,6 +151,38 @@ output::
.. latex:clearpage::
+Inspect and run a tool
+++++++++++++++++++++++
+
+Use ``AsyncTool.describe_tool()`` to return JSON metadata for a tool, including
+its function arguments. Use ``AsyncTool.run_tool(input)`` to invoke a tool
+directly with an input payload.
+
+.. code-block:: python
+
+ tool = await AsyncTool.fetch("MOVIE_SQL_TOOL")
+ print(await tool.describe_tool())
+ print(await tool.run_tool('{"query": "How many movies are there?"}'))
+
+The complete example creates a temporary PL/SQL tool, describes it, invokes it,
+and removes the temporary database objects:
+
+.. literalinclude:: ../../../samples/agent/async/tool_run_describe.py
+ :language: python
+ :lines: 14-
+
+The generated tool name and calculated age vary between runs. Representative
+output is:
+
+output::
+
+ Tool description:
+ {"tool_name": "SAMPLE_AGE_TOOL_", ...}
+ Tool result:
+
+
+.. latex:clearpage::
+
*************
``AsyncTask``
@@ -248,6 +281,76 @@ AsyncTeam
.. autoclass:: select_ai.agent.AsyncTeam
:members:
+Share an async team
++++++++++++++++++++
+
+Grant or revoke access for a database user or role with ``AsyncTeam`` methods.
+Run these methods as the team owner:
+
+.. code-block:: python
+
+ team = await AsyncTeam.fetch("MOVIE_AGENT_TEAM")
+ await team.grant_access("APP_USER")
+ await team.revoke_access("APP_USER")
+
+Team fetch and list operations use the connected user's agent-team views and do
+not currently accept an ``owner`` argument.
+
+Supervised teams
+++++++++++++++++
+
+Configure an asynchronous supervised team in the same way as a synchronous
+team. Set ``supervisor=True`` on the coordinating agent and pass its name as
+``TeamAttributes.supervisor_agent``. Keep the worker agent and task
+assignments in the team's ``agents`` list.
+
+The database creates the supervisor task when the team is created, so
+``supervisor_task`` should not be supplied by the application. Fetch the team
+after creation to read the generated value from
+``fetched.attributes.supervisor_task``.
+
+.. code-block:: python
+
+ supervisor = AsyncAgent(
+ agent_name="MOVIE_SUPERVISOR",
+ attributes=AgentAttributes(
+ profile_name="oci_ai_profile",
+ role="You supervise and coordinate the team.",
+ supervisor=True,
+ ),
+ )
+
+ team = AsyncTeam(
+ team_name="MOVIE_AGENT_TEAM",
+ attributes=TeamAttributes(
+ agents=[
+ {"name": "MOVIE_ANALYST", "task": "ANALYZE_MOVIE_TASK"}
+ ],
+ process="sequential",
+ supervisor_agent=supervisor.agent_name,
+ ),
+ )
+
+The complete asynchronous example creates the team, runs a prompt through the
+supervisor workflow, and inspects its generated supervisor task and metadata.
+``AsyncTeam.run()`` starts the team workflow; the configured supervisor agent
+coordinates the worker agent as part of that call.
+
+.. literalinclude:: ../../../samples/agent/async/team_supervisor_inspect.py
+ :language: python
+ :lines: 14-
+
+The generated names and returned JSON metadata vary between runs. Representative
+output is:
+
+output::
+
+ Team response:
+ Supervisor agent: SAMPLE_SUPERVISOR_
+ Supervisor task:
+ Team description:
+ Team tools:
+
.. latex:clearpage::
Run Team
@@ -351,6 +454,22 @@ storage, ``AsyncTeam.export_team()`` writes the specification to the location
and returns ``None``. When importing from object storage, pass the same
credential and location instead of ``specification``.
+Inspect a team
+++++++++++++++
+
+``AsyncTeam.describe_team()`` returns JSON metadata and the aggregated skills
+for a team. ``AsyncTeam.list_tools()`` returns JSON metadata for the tools
+available to that team.
+
+.. code-block:: python
+
+ team = await AsyncTeam.fetch("MOVIE_AGENT_TEAM")
+ print(await team.describe_team())
+ print(await team.list_tools())
+
+The asynchronous supervised-team sample also demonstrates both inspection
+methods.
+
.. latex:clearpage::
Lifecycle helpers
@@ -374,6 +493,106 @@ operations.
.. latex:clearpage::
+Object definitions
+******************
+
+Use ``async_get_definition(object_type, object_name)`` to asynchronously
+retrieve the canonical PL/SQL block for recreating an AI ``AGENT``, ``TASK``,
+``TOOL``, or ``TEAM``. The function returns ``None`` when the database does not
+return a definition.
+
+.. code-block:: python
+
+ from select_ai.agent import async_get_definition
+
+ definition = await async_get_definition("TASK", "ANALYZE_MOVIE_TASK")
+ print(definition)
+
+See the asynchronous definition sample for a complete create, inspect, and
+cleanup flow.
+
+.. literalinclude:: ../../../samples/agent/async/get_definition.py
+ :language: python
+ :lines: 14-
+
+The task name and exact PL/SQL formatting vary between runs and database
+versions. Representative output is:
+
+output::
+
+ BEGIN
+ DBMS_CLOUD_AI_AGENT.CREATE_TASK(...);
+ END;
+ /
+
+.. latex:clearpage::
+
+
+.. _async-agent-history:
+
+********************
+Async agent history
+********************
+
+``AsyncTeamHistory``, ``AsyncTaskHistory``, and
+``AsyncToolHistory`` provide asynchronous, read-only access to the current
+user's Select AI Agent history views. Their ``list()`` methods return async
+iterators ordered from newest to oldest.
+
+Use ``team_exec_id`` from a team event to scope task and tool history to the
+same execution. Filters such as ``team_name``, ``task_name``,
+``agent_name``, and ``tool_name`` can be used when an execution
+identifier is not available. Tool ``input`` and ``output`` values
+are decoded to Python objects when they contain valid JSON.
+
+.. code-block:: python
+
+ from select_ai.agent import (
+ AsyncTaskHistory,
+ AsyncTeamHistory,
+ AsyncToolHistory,
+ )
+
+ async for team_run in AsyncTeamHistory.list(
+ team_name="ORACLE_AI_DATABASE_AGENT",
+ limit=1,
+ ):
+ print(team_run)
+ async for task_run in AsyncTaskHistory.list(
+ team_exec_id=team_run.team_exec_id
+ ):
+ print(task_run)
+ async for tool_run in AsyncToolHistory.list(
+ team_exec_id=team_run.team_exec_id
+ ):
+ print(tool_run)
+
+The complete sample retrieves a team's latest execution and uses its
+``team_exec_id`` to retrieve the associated task and tool history:
+
+.. autoclass:: select_ai.agent.AsyncTeamHistory
+ :members:
+
+.. autoclass:: select_ai.agent.AsyncTaskHistory
+ :members:
+
+.. autoclass:: select_ai.agent.AsyncToolHistory
+ :members:
+
+.. literalinclude:: ../../../samples/agent/async/agent_history_list.py
+ :language: python
+ :lines: 14-
+
+History depends on an existing execution for the configured team. Representative
+output is:
+
+output::
+
+ TeamHistoryEvent(team_exec_id='', team_name='ORACLE_AI_DATABASE_AGENT', state='', ...)
+ TaskHistoryEvent(team_exec_id='', task_name='', state='', ...)
+ ToolHistoryEvent(invocation_id=, team_exec_id='', tool_name='', ...)
+
+.. latex:clearpage::
List Teams
++++++++++
diff --git a/doc/source/user_guide/async_profile.rst b/doc/source/user_guide/async_profile.rst
index de1c9f8..9642526 100644
--- a/doc/source/user_guide/async_profile.rst
+++ b/doc/source/user_guide/async_profile.rst
@@ -37,6 +37,7 @@ The usual async profile lifecycle is:
list.
* Create the profile with ``await select_ai.AsyncProfile(...)``.
* Reuse the profile later by name.
+* Enable or disable the profile without deleting it.
* Update profile attributes when provider settings or object scope changes.
* Delete profiles that are no longer needed.
@@ -149,6 +150,28 @@ saved database profile and raise an error if the profile does not exist:
.. latex:clearpage::
+*******************************
+Enable and disable AsyncProfile
+*******************************
+
+Use ``await AsyncProfile.disable()`` to make an existing profile unavailable
+without deleting it. Call ``await AsyncProfile.enable()`` to make it available
+again. These methods change the database profile status and return ``None``.
+
+.. literalinclude:: ../../../samples/async/profile_enable_disable.py
+ :language: python
+ :lines: 14-
+
+The sample uses ``SELECT_AI_PROFILE_NAME`` when set, otherwise it operates on
+``oci_ai_profile``. It always re-enables the profile during cleanup:
+
+output::
+
+ Disabled profile: oci_ai_profile
+ Enabled profile: oci_ai_profile
+
+.. latex:clearpage::
+
***********************
Update Async Profile
***********************
@@ -354,6 +377,83 @@ Streaming is supported by ``generate()``, ``chat()``, ``narrate()``,
``explain_sql()``, ``show_sql()``, and ``show_prompt()``. It is not supported
for ``run_sql()``, which returns a ``pandas.DataFrame``.
+.. _async-request-profile-attributes:
+
+***********************************************
+Async request-level profile attribute overrides
+***********************************************
+
+The ``attributes`` keyword accepts a mapping of profile attributes to apply to
+one async request. These values override the corresponding saved profile
+attributes for that request only; they do not update the profile in the
+database. The mapping must contain JSON-serializable values.
+
+Request-level attributes are available on the following asynchronous APIs:
+
+* ``AsyncProfile.generate()``, ``chat()``, and ``narrate()``.
+* SQL helpers: ``explain_sql()``, ``run_sql()``, ``show_sql()``, and
+ ``show_prompt()``.
+* ``AsyncSession.chat()``, ``narrate()``, ``explain_sql()``, ``run_sql()``,
+ ``show_sql()``, and ``show_prompt()``.
+* ``AsyncProfile.run_pipeline()``, which applies the same mapping to every
+ prompt/action request in the pipeline.
+* Streaming calls to the text-producing methods, by combining
+ ``stream=True`` with ``attributes=...``. ``run_sql()`` does not support
+ streaming.
+
+The mapping can include attributes such as ``additional_instructions``,
+the integer ``seed``, and ``source_language`` or ``target_language``. Use
+``ProfileAttributes`` for profile-wide defaults; use this mapping when an
+override should apply only to the current request.
+
+For ``translate()``, pass language values through its named
+``source_language`` and ``target_language`` arguments. The ``attributes``
+mapping described here applies to the generate/action APIs.
+
+.. code-block:: python
+
+ request_attributes = {
+ "additional_instructions": "Answer in one sentence.",
+ "seed": 42,
+ "source_language": "en",
+ "target_language": "de",
+ }
+
+ response = await async_profile.chat(
+ prompt="What is Oracle Cloud Infrastructure?",
+ attributes=request_attributes,
+ )
+
+For async streaming, await the method call first and then iterate over the
+returned async iterator. The same mapping can be used with an async session:
+
+.. code-block:: python
+
+ chunks = await async_profile.chat(
+ prompt="Summarize Oracle Cloud Infrastructure.",
+ stream=True,
+ attributes=request_attributes,
+ )
+ async for chunk in chunks:
+ print(chunk, end="")
+
+ async with async_profile.chat_session(conversation) as session:
+ response = await session.chat(
+ prompt="Give one more detail.",
+ attributes=request_attributes,
+ )
+
+The following sample uses ``show_prompt()`` to verify that the request-only
+``additional_instructions`` value reached the generated prompt:
+
+.. literalinclude:: ../../../samples/async/profile_request_attributes.py
+ :language: python
+ :lines: 14-
+
+output::
+
+ Request attributes applied: True
+
.. latex:clearpage::
**************************
@@ -407,6 +507,35 @@ output::
Translate
***********
+``AsyncProfile.translate()`` accepts optional ``source_language`` and
+``target_language`` arguments. Per-call values take precedence over the
+profile's ``source_language`` and ``target_language`` defaults.
+
+If ``source_language`` is omitted for both the call and the profile, source
+language detection is delegated to the translation provider. If
+``target_language`` is omitted, the profile's default is used; a target
+language must be supplied in one of those two places.
+
+Configure profile-level defaults with ``ProfileAttributes`` when creating or
+updating a profile:
+
+.. code-block:: python
+
+ language_defaults = select_ai.ProfileAttributes(
+ source_language="en",
+ target_language="de",
+ )
+
+ await async_profile.set_attributes(language_defaults)
+ print(await async_profile.translate(text="Thank you"))
+
+When only the target is supplied at the call site, the provider can detect the
+source language:
+
+.. code-block:: python
+
+ print(await async_profile.translate(text="Thank you", target_language="de"))
+
.. literalinclude:: ../../../samples/async/profile_translate.py
:language: python
@@ -422,6 +551,20 @@ output::
Async pipeline
*********************
+``AsyncProfile.run_pipeline()`` also accepts ``attributes=...``. The mapping
+is applied to every prompt/action request in the pipeline, while the saved
+profile remains unchanged.
+
+.. code-block:: python
+
+ results = await async_profile.run_pipeline(
+ prompt_specifications,
+ attributes={
+ "additional_instructions": "Keep each answer concise.",
+ "seed": 42,
+ },
+ )
+
.. literalinclude:: ../../../samples/async/profile_pipeline.py
:language: python
:lines: 14-
@@ -455,8 +598,22 @@ output::
List profiles asynchronously
****************************
-Profile listing returns profiles visible to the connected database user. The
-async list API returns an async iterator.
+Profile listing returns profiles visible to the connected database user. Pass
+``owner`` to list profiles from a specific schema, including a profile shared
+with the connected user. Returned objects include ``owner`` and the
+owner-qualified ``qualified_name`` property. The async list API returns an
+async iterator.
+
+.. code-block:: python
+
+ profile = await select_ai.AsyncProfile.fetch(
+ "OCI_AI_PROFILE",
+ owner="APP_OWNER",
+ )
+ print(profile.qualified_name)
+
+ async for profile in select_ai.AsyncProfile.list(owner="APP_OWNER"):
+ print(profile.qualified_name)
.. literalinclude:: ../../../samples/async/profiles_list.py
:language: python
diff --git a/doc/source/user_guide/cli.rst b/doc/source/user_guide/cli.rst
index 76e2855..223675e 100644
--- a/doc/source/user_guide/cli.rst
+++ b/doc/source/user_guide/cli.rst
@@ -15,9 +15,10 @@ write Python code. It can help users check whether a profile is configured
correctly, inspect generated SQL, try prompts during development, or interact
with a curated profile through a simple command.
-The CLI works with Select AI profiles. RAG is supported when the selected
-profile is already configured with a vector index. Additional CLI options and
-workflows will be added in upcoming releases as the CLI evolves.
+The CLI works with Select AI profiles and can also run the Select AI A2A
+standalone server, dynamic gateway, internal worker, and Agent Card generator.
+RAG is supported when the selected profile is already configured with a vector
+index.
.. only:: html
@@ -32,15 +33,23 @@ workflows will be added in upcoming releases as the CLI evolves.
:width: 100%
The package provides an optional ``select-ai`` command line tool. Install the
-CLI extra to use it, including the A2A server commands:
+CLI extra to use profile, SQL, chat, and A2A commands. The ``a2a`` extra is an
+alias that makes the A2A dependency set explicit:
.. code-block:: bash
pip install 'select_ai[cli]'
+ # Equivalent installation for an A2A deployment:
+ pip install 'select_ai[a2a]'
Use ``select-ai --help`` to view the available command groups and
``select-ai --help`` to view options for a specific command.
+The complete A2A guide covers the standalone server, dynamic gateway, Agent
+Card discovery, A2A protocol compatibility, task execution, persistent
+Oracle-backed state, and Google Cloud deployment. See :ref:`A2A Integration
+`.
+
Set the database connection details as environment variables, or pass them as
command line options:
@@ -53,7 +62,10 @@ command line options:
Connection options
==================
-All CLI commands accept the same database connection options:
+Database-backed commands accept the following connection options. The
+standalone ``select-ai a2a serve`` command uses them directly; the dynamic
+gateway receives DSN, username, and password through its A2UI connection form,
+and ``a2a worker`` receives those values internally when it opens a session.
.. list-table:: Connection options
:header-rows: 1
@@ -74,8 +86,10 @@ All CLI commands accept the same database connection options:
- ``SELECT_AI_WALLET_PASSWORD``
If ``--password`` and ``SELECT_AI_PASSWORD`` are not set, the CLI prompts for
-the database password. Wallet options are optional and are only needed for
-wallet-based database connections.
+the database password. Wallet options are optional and apply to database
+commands that support wallet connections, including standalone
+``select-ai a2a serve``. The dynamic gateway session path currently accepts
+only DSN, username, and password.
Interactive chat
================
@@ -181,6 +195,125 @@ Use ``--pattern`` to filter names with a regular expression.
``--credential-name`` when the location URI requires an object storage
credential.
+A2A commands
+============
+
+The ``a2a`` command group exposes the cloud-neutral A2A runtimes. Use
+``select-ai a2a --help`` or a command-specific ``--help`` option to see the
+current defaults:
+
+.. code-block:: bash
+
+ select-ai a2a --help
+ select-ai a2a serve --help
+ select-ai a2a gateway --help
+ select-ai a2a worker --help
+ select-ai a2a agent-card --help
+
+.. list-table:: A2A CLI commands
+ :header-rows: 1
+ :widths: 34 66
+ :align: left
+
+ * - Command
+ - Purpose
+ * - ``select-ai a2a serve``
+ - Start a standalone A2A HTTP server for one configured database AI Agent
+ Team. It accepts the database connection and optional wallet options.
+ * - ``select-ai a2a gateway``
+ - Start the public dynamic A2A/A2UI gateway. It uses Consul to discover
+ workers and does not connect to Oracle directly.
+ * - ``select-ai a2a worker``
+ - Start the internal worker that registers with Consul and creates an
+ isolated database-bearing child process for each submitted connection.
+ * - ``select-ai a2a agent-card``
+ - Print a Gemini Enterprise-compatible A2A v0.3 Agent Card without
+ starting a server.
+
+Standalone server
+-----------------
+
+Start one configured database AI Agent Team. The team, user, password, and DSN
+can be passed as options or through the ``SELECT_AI_*`` environment variables:
+
+.. code-block:: bash
+
+ export SELECT_AI_USER=select_ai_user
+ export SELECT_AI_PASSWORD='database-password'
+ export SELECT_AI_DB_CONNECT_STRING=db2025adb_medium
+
+ select-ai a2a serve \
+ --team ORACLE_AI_DATABASE_AGENT \
+ --host 0.0.0.0 \
+ --port 8000 \
+ --public-url https://a2a.example.com
+
+.. only:: html
+
+ .. image:: /image/select_ai_a2a_server_demo.gif
+ :alt: Select AI A2A server CLI demo
+ :width: 100%
+
+Important options are ``--team`` (required), ``--host``, ``--port``,
+``--public-url``, ``--description``, and ``--pool-max-size``. The
+``--wallet-location`` and ``--wallet-password`` options configure an Oracle
+wallet for this standalone path. If no password is provided, the command
+prompts for it.
+
+Dynamic gateway and worker
+--------------------------
+
+The gateway and worker are separate processes. Start a worker for each runtime
+that can reach Consul and Oracle, then start the public gateway. The worker has
+no database credentials at startup; the gateway opens a temporary session by
+passing the A2UI form values to the worker.
+
+.. code-block:: bash
+
+ CONSUL_HTTP_URL=http://127.0.0.1:8500 \
+ WORKER_ID=local-worker \
+ WORKER_ADDRESS=127.0.0.1 \
+ WORKER_PORT=8081 \
+ select-ai a2a worker --host 127.0.0.1 --port 8081
+
+ select-ai a2a gateway \
+ --host 127.0.0.1 \
+ --port 8000 \
+ --agent-url http://127.0.0.1:8000 \
+ --consul-url http://127.0.0.1:8500
+
+The worker options are ``--host``, ``--port``, ``--session-ttl-seconds``, and
+``--session-start-timeout-seconds``. Its registration can be configured with
+the ``CONSUL_HTTP_URL``, ``WORKER_ID``, ``WORKER_ADDRESS``, ``WORKER_PORT``,
+and optional ``WORKER_ENDPOINT`` environment variables. The worker's
+``--tls-cert-file``, ``--tls-key-file``, and ``--tls-ca-file`` options enable
+gateway-to-worker mTLS; provide all three together.
+
+The gateway options are ``--agent-url`` (required, or ``AGENT_URL``),
+``--consul-url`` (or ``CONSUL_HTTP_URL``), ``--worker-service`` (or
+``WORKER_SERVICE``), and ``--session-ttl-seconds`` (or
+``SESSION_TTL_SECONDS``). The optional
+``--worker-tls-ca-file``, ``--worker-tls-cert-file``, and
+``--worker-tls-key-file`` options configure the gateway's client side of
+gateway-to-worker mTLS. These TLS settings protect the internal HTTP hop and
+are separate from Oracle wallet authentication.
+
+Agent Card generation
+---------------------
+
+Print the portable A2A v0.3 card for a standalone public service:
+
+.. code-block:: bash
+
+ select-ai a2a agent-card \
+ --team ORACLE_AI_DATABASE_AGENT \
+ --public-url https://a2a.example.com
+
+``--team`` and ``--public-url`` are required; use ``--description`` to replace
+the default description. See :ref:`A2A Integration ` for protocol
+compatibility, persistent state, gateway session behavior, wallet support, and
+Google Cloud deployment details.
+
Command summary
===============
@@ -207,3 +340,11 @@ Command summary
- Summarize inline content, a file, or a URI.
* - ``select-ai profile translate``
- Translate text with a saved profile.
+ * - ``select-ai a2a serve``
+ - Start the standalone A2A server.
+ * - ``select-ai a2a gateway``
+ - Start the public dynamic A2A gateway.
+ * - ``select-ai a2a worker``
+ - Start the internal dynamic-session worker.
+ * - ``select-ai a2a agent-card``
+ - Print an A2A v0.3 Agent Card.
diff --git a/doc/source/user_guide/concurrent_prompt_processing.rst b/doc/source/user_guide/concurrent_prompt_processing.rst
index 1f2e165..60b8aac 100644
--- a/doc/source/user_guide/concurrent_prompt_processing.rst
+++ b/doc/source/user_guide/concurrent_prompt_processing.rst
@@ -143,7 +143,8 @@ tools or services that can forward each answer as soon as it is ready.
This recipe uses ``AsyncProfile.run_pipeline()`` to send multiple
prompt/action pairs in one database round trip. This is different from Python
task concurrency: the application submits a batch and receives the batch
-results when the pipeline completes.
+results when the pipeline completes. Pass ``attributes=...`` to apply the
+same request-level profile overrides to every item in the pipeline.
.. literalinclude:: ../../../recipes/concurrent_prompt_processing/async_pipeline.py
:language: python
diff --git a/doc/source/user_guide/conversation.rst b/doc/source/user_guide/conversation.rst
index a9db8f7..dd427b0 100644
--- a/doc/source/user_guide/conversation.rst
+++ b/doc/source/user_guide/conversation.rst
@@ -28,8 +28,18 @@ The usual lifecycle is:
``Conversation Object model``
*****************************
.. _conversationfig:
-.. figure:: /image/conversation.png
- :alt: Select AI Conversation
+
+.. only:: html
+
+ .. figure:: /image/conversation_object_model.svg
+ :alt: Select AI Conversation object model
+ :width: 100%
+
+.. only:: latex
+
+ .. figure:: /image/conversation_object_model.png
+ :alt: Select AI Conversation object model
+ :width: 100%
.. latex:clearpage::
@@ -103,6 +113,14 @@ The synchronous API is used with ``select_ai.connect()`` or
length.
* - ``list()``
- Iterate over conversations visible to the current user.
+ * - ``list_prompts()``
+ - Iterate over prompts and responses stored in this conversation.
+ * - ``delete_prompt(conversation_prompt_id, force=False)``
+ - Delete one stored prompt. Use ``force=True`` to ignore a missing prompt.
+ * - ``add_tag(tag_key, tag_value)``
+ - Add a tag or update the value for an existing tag key.
+ * - ``remove_tag(tag_key, force=False)``
+ - Remove a tag. Use ``force=True`` to ignore a missing tag.
* - ``delete(force=False)``
- Drop the conversation. Use ``force=True`` to ignore missing-conversation
errors.
@@ -157,6 +175,50 @@ output::
.. latex:clearpage::
+Prompt history and tags
++++++++++++++++++++++++
+
+``Conversation.list_prompts()`` returns an iterator of
+``ConversationPrompt`` objects in creation order. Each object includes the
+prompt identifier, prompt text, response text, conversation metadata, and
+timestamps. Use the identifier with ``Conversation.delete_prompt()`` to remove
+a stored prompt from the conversation.
+
+Conversation tags are key-value pairs. ``Conversation.add_tag()`` creates a tag
+or updates the value for an existing key. ``Conversation.remove_tag()`` removes
+a tag; pass ``force=True`` when a missing tag should not raise an error.
+
+.. autoclass:: select_ai.ConversationPrompt
+ :members:
+
+.. code-block:: python
+
+ conversation.add_tag("PROJECT", "SELECT_AI")
+ prompts = list(conversation.list_prompts())
+ for prompt in prompts:
+ print(prompt.conversation_prompt_id, prompt.prompt)
+ conversation.delete_prompt(prompts[-1].conversation_prompt_id)
+ conversation.remove_tag("PROJECT")
+
+The complete sample creates a conversation, stores one prompt through a chat
+session, lists the resulting ``ConversationPrompt``, deletes it, and exercises
+tag creation, update, and removal:
+
+.. literalinclude:: ../../../samples/conversation_prompts_tags.py
+ :language: python
+ :lines: 14-
+
+The conversation ID, prompt ID, and model response vary between runs.
+Representative output is:
+
+output::
+
+ conversation history works.
+ Prompt : Reply with exactly: conversation history works.
+ Prompts after deletion: []
+
+.. latex:clearpage::
+
List conversations
++++++++++++++++++
@@ -223,6 +285,14 @@ The async API mirrors the synchronous API and is used with
- ``async for conversation in AsyncConversation.list()``
* - ``Conversation.delete(...)``
- ``await AsyncConversation.delete(...)``
+ * - ``Conversation.list_prompts()``
+ - ``async for prompt in AsyncConversation.list_prompts()``
+ * - ``Conversation.delete_prompt(...)``
+ - ``await AsyncConversation.delete_prompt(...)``
+ * - ``Conversation.add_tag(...)``
+ - ``await AsyncConversation.add_tag(...)``
+ * - ``Conversation.remove_tag(...)``
+ - ``await AsyncConversation.remove_tag(...)``
* - ``with profile.chat_session(...)``
- ``async with async_profile.chat_session(...)``
@@ -255,6 +325,42 @@ output::
.. latex:clearpage::
+Async prompt history and tags
++++++++++++++++++++++++++++++
+
+``AsyncConversation.list_prompts()`` is an async generator that yields the same
+``ConversationPrompt`` objects as the synchronous API. Await
+``delete_prompt()``, ``add_tag()``, and ``remove_tag()`` to manage stored
+prompts and conversation tags. As with the synchronous API, use ``force=True``
+when deleting a missing prompt or removing a missing tag should succeed.
+
+.. code-block:: python
+
+ await conversation.add_tag("PROJECT", "SELECT_AI")
+ prompts = [prompt async for prompt in conversation.list_prompts()]
+ for prompt in prompts:
+ print(prompt.conversation_prompt_id, prompt.prompt)
+ await conversation.delete_prompt(prompts[-1].conversation_prompt_id)
+ await conversation.remove_tag("PROJECT")
+
+The asynchronous sample performs the same prompt-history and tag-management
+flow using ``AsyncConversation``:
+
+.. literalinclude:: ../../../samples/async/conversation_prompts_tags.py
+ :language: python
+ :lines: 13-
+
+The conversation ID, prompt ID, and model response vary between runs.
+Representative output is:
+
+output::
+
+ conversation history works.
+ Prompt : Reply with exactly: conversation history works.
+ Prompts after deletion: []
+
+.. latex:clearpage::
+
Async list conversations
++++++++++++++++++++++++
diff --git a/doc/source/user_guide/credential.rst b/doc/source/user_guide/credential.rst
index 19ad639..f8c97cf 100644
--- a/doc/source/user_guide/credential.rst
+++ b/doc/source/user_guide/credential.rst
@@ -25,6 +25,7 @@ formats.
:header-rows: 1
:widths: 30 70
:align: left
+ :class: longtable
* - AI provider
- Credential format
@@ -108,8 +109,13 @@ Pass ``replace=True`` when you want to recreate an existing credential with the
same name. Without ``replace=True``, creating a credential that already exists
raises a database error.
-Sync API
-++++++++
+Pass ``public_synonym=True`` to also create a public synonym with the same name
+as the credential. This requires the ``CREATE PUBLIC SYNONYM`` system
+privilege. The credential samples create the synonym and remove it during the
+delete step.
+
+Create Sync API
++++++++++++++++
.. literalinclude:: ../../../samples/create_ai_credential.py
:language: python
@@ -121,8 +127,8 @@ output::
.. latex:clearpage::
-Async API
-+++++++++
+Create Async API
+++++++++++++++++
.. literalinclude:: ../../../samples/async/create_ai_credential.py
:language: python
@@ -140,10 +146,11 @@ Delete credential
Use ``select_ai.delete_credential(...)`` to drop a credential that is no longer
needed. Pass ``force=True`` when cleanup should succeed even if the credential
-does not exist.
+does not exist. Pass ``public_synonym=True`` to drop the credential's public
+synonym as well; this requires the ``DROP PUBLIC SYNONYM`` system privilege.
-Sync API
-++++++++
+Delete Sync API
++++++++++++++++
.. literalinclude:: ../../../samples/delete_ai_credential.py
:language: python
@@ -153,10 +160,8 @@ output::
Deleted credential: my_oci_ai_profile_key
-.. latex:clearpage::
-
-Async API
-+++++++++
+Delete Async API
+++++++++++++++++
.. literalinclude:: ../../../samples/async/delete_ai_credential.py
:language: python
@@ -165,3 +170,39 @@ Async API
output::
Deleted credential: my_oci_ai_profile_key
+
+.. latex:clearpage::
+
+**************************
+Credential access
+**************************
+
+Use ``select_ai.grant_credential_access(...)`` and
+``select_ai.revoke_credential_access(...)`` to grant or revoke credential
+access for a database user or role. The asynchronous equivalents are
+``async_grant_credential_access(...)`` and
+``async_revoke_credential_access(...)``.
+
+.. code-block:: python
+
+ select_ai.grant_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+ select_ai.revoke_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+
+Use ``await`` with the asynchronous equivalents:
+
+.. code-block:: python
+
+ await select_ai.async_grant_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+ await select_ai.async_revoke_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
diff --git a/doc/source/user_guide/introduction.rst b/doc/source/user_guide/introduction.rst
index ab0774d..b6ce86f 100644
--- a/doc/source/user_guide/introduction.rst
+++ b/doc/source/user_guide/introduction.rst
@@ -59,6 +59,10 @@ Most workflows use the same building blocks:
* - Credential
- Store provider and service secrets securely in Oracle Database.
- :ref:`Credential `
+ * - Sharing and ownership
+ - Share profiles, vector indexes, teams, and credentials with users or
+ roles and address objects owned by another schema.
+ - :ref:`Sharing and ownership `
* - Profile
- Combine provider, credential, database object scope, and generation
options into a reusable Select AI profile.
diff --git a/doc/source/user_guide/profile.rst b/doc/source/user_guide/profile.rst
index 9f1749d..8521790 100644
--- a/doc/source/user_guide/profile.rst
+++ b/doc/source/user_guide/profile.rst
@@ -26,6 +26,7 @@ The usual profile lifecycle is:
list.
* Create the profile with ``select_ai.Profile(...)``.
* Reuse the profile later by name.
+* Enable or disable the profile without deleting it.
* Update profile attributes when provider settings or object scope changes.
* Delete profiles that are no longer needed.
@@ -65,8 +66,18 @@ Profile Object Model
********************
.. _profilefig:
-.. figure:: /image/profile_provider.png
- :alt: Select AI Profile and Providers
+
+.. only:: html
+
+ .. figure:: /image/profile_object_model.svg
+ :alt: Select AI Profile object model
+ :width: 100%
+
+.. only:: latex
+
+ .. figure:: /image/profile_object_model.png
+ :alt: Select AI Profile object model
+ :width: 100%
.. latex:clearpage::
@@ -152,6 +163,28 @@ database profile and raise an error if the profile does not exist:
.. latex:clearpage::
+**************************
+Enable and disable Profile
+**************************
+
+Use ``Profile.disable()`` to make an existing profile unavailable without
+deleting it. Call ``Profile.enable()`` to make it available again. These
+methods change the database profile status and return ``None``.
+
+.. literalinclude:: ../../../samples/profile_enable_disable.py
+ :language: python
+ :lines: 14-
+
+The sample uses ``SELECT_AI_PROFILE_NAME`` when set, otherwise it operates on
+``oci_ai_profile``. It always re-enables the profile during cleanup:
+
+output::
+
+ Disabled profile: oci_ai_profile
+ Enabled profile: oci_ai_profile
+
+.. latex:clearpage::
+
**************************
Update Profile
**************************
@@ -305,6 +338,79 @@ Streaming is supported by ``generate()``, ``chat()``, ``narrate()``,
``explain_sql()``, ``show_sql()``, and ``show_prompt()``. It is not supported
for ``run_sql()``, which returns a ``pandas.DataFrame``.
+.. _request-profile-attributes:
+
+*****************************************
+Request-level profile attribute overrides
+*****************************************
+
+The ``attributes`` keyword accepts a mapping of profile attributes to apply to
+one request. These values override the corresponding saved profile attributes
+for that request only; they do not update the profile in the database. The
+mapping must contain JSON-serializable values.
+
+Request-level attributes are available on the following synchronous APIs:
+
+* ``Profile.generate()``, ``chat()``, and ``narrate()``.
+* SQL helpers: ``explain_sql()``, ``run_sql()``, ``show_sql()``, and
+ ``show_prompt()``.
+* ``Session.chat()``, ``narrate()``, ``explain_sql()``, ``run_sql()``,
+ ``show_sql()``, and ``show_prompt()``.
+* Streaming calls to the text-producing methods, by combining
+ ``stream=True`` with ``attributes=...``. ``run_sql()`` does not support
+ streaming.
+
+The mapping can include attributes such as ``additional_instructions``,
+the integer ``seed``, and ``source_language`` or ``target_language``. Use
+``ProfileAttributes`` for profile-wide defaults; use this mapping when an
+override should apply only to the current request.
+
+For ``translate()``, pass language values through its named
+``source_language`` and ``target_language`` arguments. The ``attributes``
+mapping described here applies to the generate/action APIs.
+
+.. code-block:: python
+
+ request_attributes = {
+ "additional_instructions": "Answer in one sentence.",
+ "seed": 42,
+ "source_language": "en",
+ "target_language": "de",
+ }
+
+ response = profile.chat(
+ prompt="What is Oracle Cloud Infrastructure?",
+ attributes=request_attributes,
+ )
+
+The same mapping can be used with streaming and session requests:
+
+.. code-block:: python
+
+ for chunk in profile.chat(
+ prompt="Summarize Oracle Cloud Infrastructure.",
+ stream=True,
+ attributes=request_attributes,
+ ):
+ print(chunk, end="")
+
+ with profile.chat_session(conversation) as session:
+ response = session.chat(
+ prompt="Give one more detail.",
+ attributes=request_attributes,
+ )
+
+The following sample uses ``show_prompt()`` to verify that the request-only
+``additional_instructions`` value reached the generated prompt:
+
+.. literalinclude:: ../../../samples/profile_request_attributes.py
+ :language: python
+ :lines: 14-
+
+output::
+
+ Request attributes applied: True
+
.. latex:clearpage::
**************************
@@ -358,6 +464,35 @@ output::
Translate
***********
+``Profile.translate()`` accepts optional ``source_language`` and
+``target_language`` arguments. Per-call values take precedence over the
+profile's ``source_language`` and ``target_language`` defaults.
+
+If ``source_language`` is omitted for both the call and the profile, source
+language detection is delegated to the translation provider. If
+``target_language`` is omitted, the profile's default is used; a target
+language must be supplied in one of those two places.
+
+Configure profile-level defaults with ``ProfileAttributes`` when creating or
+updating a profile:
+
+.. code-block:: python
+
+ language_defaults = select_ai.ProfileAttributes(
+ source_language="en",
+ target_language="de",
+ )
+
+ profile.set_attributes(language_defaults)
+ print(profile.translate(text="Thank you"))
+
+When only the target is supplied at the call site, the provider can detect the
+source language:
+
+.. code-block:: python
+
+ print(profile.translate(text="Thank you", target_language="de"))
+
.. literalinclude:: ../../../samples/profile_translate.py
:language: python
:lines: 14-
@@ -373,8 +508,21 @@ List profiles
**************************
Profile listing returns profiles visible to the connected database user.
-Instantiate ``Profile`` with one of the returned names to reuse the saved
-profile.
+Pass ``owner`` to list profiles from a specific schema, including a profile
+shared with the connected user. Each returned object includes ``owner`` and
+the owner-qualified ``qualified_name`` property. Instantiate ``Profile`` with
+one of the returned names to reuse the saved profile.
+
+.. code-block:: python
+
+ profile = select_ai.Profile.fetch(
+ "OCI_AI_PROFILE",
+ owner="APP_OWNER",
+ )
+ print(profile.qualified_name)
+
+ for profile in select_ai.Profile.list(owner="APP_OWNER"):
+ print(profile.qualified_name)
.. literalinclude:: ../../../samples/profiles_list.py
:language: python
diff --git a/doc/source/user_guide/profile_attributes.rst b/doc/source/user_guide/profile_attributes.rst
index fba5783..047132a 100644
--- a/doc/source/user_guide/profile_attributes.rst
+++ b/doc/source/user_guide/profile_attributes.rst
@@ -79,9 +79,10 @@ Attribute groups
for requests that use the profile.
* - ``source_language``, ``target_language``
- Set default languages for ``Profile.translate()`` and
- ``AsyncProfile.translate()``. If no source language is configured or
- supplied per call, the provider detects it. A target language must be
- supplied either per call or in the profile.
+ ``AsyncProfile.translate()``. Per-call values override these defaults.
+ If no source language is configured or supplied per call, the provider
+ detects it. A target language must be supplied either per call or in the
+ profile.
* - ``vector_index_name``, ``enable_sources``,
``enable_source_offsets``, ``enable_custom_source_uri``
- Configures retrieval-augmented generation and source reporting for
diff --git a/doc/source/user_guide/provider.rst b/doc/source/user_guide/provider.rst
index f75f007..200b71c 100644
--- a/doc/source/user_guide/provider.rst
+++ b/doc/source/user_guide/provider.rst
@@ -131,6 +131,115 @@ Custom provider endpoint:
prompt="How many customers do I have?",
)
+Cloud provider profile samples
+------------------------------
+
+The repository includes complete profile-creation samples for AWS Bedrock,
+Azure OpenAI, and Google Gemini. Each sample:
+
+* grants the Select AI database user HTTP access to the provider endpoint;
+* creates or replaces a provider credential; and
+* creates or replaces a profile, then sends a test chat request.
+
+Run the samples from the repository root after setting the common database
+variables:
+
+* ``SELECT_AI_ADMIN_USER`` and ``SELECT_AI_ADMIN_PASSWORD``;
+* ``SELECT_AI_USER`` and ``SELECT_AI_PASSWORD``;
+* ``SELECT_AI_DB_CONNECT_STRING``; and
+* the provider-specific API key or access-key variables described below.
+
+Do not store production secrets in source files or commit them to the
+repository. Adjust the sample region, model, resource, deployment, and object
+names for the provider account and database used by your application.
+
+AWS Bedrock
++++++++++++
+
+The `AWS Bedrock profile sample `__
+uses ``AWSProvider`` with the following provider settings:
+
+* ``region``: AWS Bedrock region;
+* ``model``: text-generation model identifier; and
+* ``embedding_model``: embedding model identifier.
+
+Set ``AWS_ACCESS_KEY_ID`` and ``AWS_SECRET_ACCESS_KEY`` before running it:
+
+.. code-block:: bash
+
+ python samples/profile_create_aws.py
+
+.. literalinclude:: ../../../samples/profile_create_aws.py
+ :language: python
+ :lines: 8-
+
+The sample creates ``aws_bedrock_meta_prf`` and prints the saved attributes
+and a test response:
+
+output::
+
+ Created profile: aws_bedrock_meta_prf
+ {'credential_name': 'AWS_CRED', 'provider': AWSProvider(...), ...}
+ Chat response: AWS chat succeeded.
+
+Azure OpenAI
+++++++++++++
+
+The `Azure OpenAI profile sample `__
+uses ``AzureProvider`` with the following provider settings:
+
+* ``azure_resource_name``: Azure OpenAI resource name;
+* ``azure_deployment_name``: text-generation deployment name; and
+* ``azure_embedding_deployment_name``: embedding deployment name.
+
+Set ``AZURE_API_KEY`` before running it:
+
+.. code-block:: bash
+
+ python samples/profile_create_azure.py
+
+.. literalinclude:: ../../../samples/profile_create_azure.py
+ :language: python
+ :lines: 8-
+
+The sample creates ``azureai_prf``, fetches it to verify the saved profile,
+and prints a test response:
+
+output::
+
+ Profile(profile_name=azureai_prf, ...)
+ Created profile: azureai_prf
+ {'credential_name': 'AZUREAI_CRED', 'provider': AzureProvider(...), ...}
+ Chat response: Azure chat succeeded.
+
+Google Gemini (GCP)
++++++++++++++++++++
+
+The `Google Gemini profile sample `__
+uses ``GoogleProvider`` with the following provider settings:
+
+* ``model``: Gemini text-generation model identifier; and
+* ``embedding_model``: Gemini embedding model identifier.
+
+Set ``GOOGLE_API_KEY`` before running it:
+
+.. code-block:: bash
+
+ python samples/profile_create_gcp.py
+
+.. literalinclude:: ../../../samples/profile_create_gcp.py
+ :language: python
+ :lines: 8-
+
+The sample creates ``google_gemini_3_6_flash`` and prints the saved attributes
+and a test response:
+
+output::
+
+ Created profile: google_gemini_3_6_flash
+ {'credential_name': 'GOOGLE_CRED', 'provider': GoogleProvider(...), ...}
+ Chat response: GCP chat succeeded.
+
.. latex:clearpage::
``Provider``
diff --git a/doc/source/user_guide/sharing.rst b/doc/source/user_guide/sharing.rst
new file mode 100644
index 0000000..e05af91
--- /dev/null
+++ b/doc/source/user_guide/sharing.rst
@@ -0,0 +1,258 @@
+.. _sharing:
+
+**********************
+Sharing and ownership
+**********************
+
+Select AI objects can be shared with a database user or role. Sharing must be
+performed by the object owner, and the grantee must have the package and
+database privileges required to use the object.
+
+The sharing APIs are available in both synchronous and asynchronous forms:
+
+.. list-table:: Sharing API summary
+ :header-rows: 1
+ :widths: 25 37 38
+ :align: left
+
+ * - Object
+ - Synchronous API
+ - Asynchronous API
+ * - Profile
+ - ``Profile.grant_access()`` and ``revoke_access()``
+ - ``AsyncProfile.grant_access()`` and ``revoke_access()``
+ * - Vector index
+ - ``VectorIndex.grant_access()`` and ``revoke_access()``
+ - ``AsyncVectorIndex.grant_access()`` and ``revoke_access()``
+ * - Agent team
+ - ``Team.grant_access()`` and ``revoke_access()``
+ - ``AsyncTeam.grant_access()`` and ``revoke_access()``
+ * - Credential
+ - ``grant_credential_access()`` and ``revoke_credential_access()``
+ - ``async_grant_credential_access()`` and
+ ``async_revoke_credential_access()``
+
+Each access method accepts a database user or role name. Access methods return
+``None`` after the database grant or revoke operation completes.
+
+Profiles
+========
+
+``Profile.fetch()`` and ``Profile.list()`` accept an optional ``owner``
+argument. If ``owner`` is omitted, the current schema is used. The fetched
+object records its owner in ``profile.owner`` and exposes an owner-qualified
+name through ``profile.qualified_name``.
+
+.. code-block:: python
+
+ profile = select_ai.Profile.fetch(
+ "OCI_AI_PROFILE",
+ owner="APP_OWNER",
+ )
+ print(profile.owner)
+ print(profile.qualified_name)
+
+ profiles = select_ai.Profile.list(
+ profile_name_pattern="^OCI_AI_PROFILE$",
+ owner="APP_OWNER",
+ )
+ for profile in profiles:
+ print(profile.qualified_name)
+
+Grant and revoke access on the owner-side object:
+
+.. code-block:: python
+
+ profile.grant_access("APP_USER")
+ profile.revoke_access("APP_USER")
+
+The asynchronous APIs use the same ``owner`` argument and return an async
+iterator from ``AsyncProfile.list()``:
+
+.. code-block:: python
+
+ profile = await select_ai.AsyncProfile.fetch(
+ "OCI_AI_PROFILE",
+ owner="APP_OWNER",
+ )
+ profiles = [
+ item
+ async for item in select_ai.AsyncProfile.list(
+ "^OCI_AI_PROFILE$",
+ owner="APP_OWNER",
+ )
+ ]
+ print(profile.qualified_name)
+ print([item.qualified_name for item in profiles])
+
+ await profile.grant_access("APP_USER")
+ await profile.revoke_access("APP_USER")
+
+Vector indexes
+==============
+
+``VectorIndex.fetch()`` and ``VectorIndex.list()`` also accept an optional
+``owner`` argument. A vector index exposes ``owner`` and the owner-qualified
+``qualified_name`` property in the same way as a profile.
+
+.. code-block:: python
+
+ index = select_ai.VectorIndex.fetch(
+ "PRODUCT_DOCS",
+ owner="APP_OWNER",
+ )
+ indexes = select_ai.VectorIndex.list(
+ index_name_pattern="^PRODUCT_DOCS$",
+ owner="APP_OWNER",
+ )
+ print(index.qualified_name)
+ print([item.qualified_name for item in indexes])
+
+ index.grant_access("APP_USER")
+ index.revoke_access("APP_USER")
+
+The async form mirrors the synchronous API:
+
+.. code-block:: python
+
+ index = await select_ai.AsyncVectorIndex.fetch(
+ "PRODUCT_DOCS",
+ owner="APP_OWNER",
+ )
+ indexes = [
+ item
+ async for item in select_ai.AsyncVectorIndex.list(
+ "^PRODUCT_DOCS$",
+ owner="APP_OWNER",
+ )
+ ]
+ await index.grant_access("APP_USER")
+ await index.revoke_access("APP_USER")
+
+Agent teams
+===========
+
+Agent teams support synchronous and asynchronous access grants and revokes:
+
+.. code-block:: python
+
+ from select_ai.agent import Team
+
+ team = Team.fetch("MOVIE_AGENT_TEAM")
+ team.grant_access("APP_USER")
+ team.revoke_access("APP_USER")
+
+.. code-block:: python
+
+ from select_ai.agent import AsyncTeam
+
+ team = await AsyncTeam.fetch("MOVIE_AGENT_TEAM")
+ await team.grant_access("APP_USER")
+ await team.revoke_access("APP_USER")
+
+The current team APIs do not accept an ``owner`` argument. Team fetch and list
+operations use the connected user's agent-team views; owner-qualified names
+are currently provided for profiles and vector indexes.
+
+Credentials
+===========
+
+Credential access is exposed as module-level functions because credentials are
+owned in the current schema:
+
+.. code-block:: python
+
+ select_ai.grant_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+ select_ai.revoke_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+
+Use the asynchronous equivalents with ``await``:
+
+.. code-block:: python
+
+ await select_ai.async_grant_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+ await select_ai.async_revoke_credential_access(
+ "MY_PROVIDER_CREDENTIAL",
+ "APP_USER",
+ )
+
+Credential creation and deletion optionally manage a public synonym with the
+same name as the credential. Creating or dropping a public synonym requires the
+corresponding ``CREATE PUBLIC SYNONYM`` or ``DROP PUBLIC SYNONYM`` privilege:
+
+.. code-block:: python
+
+ select_ai.create_credential(
+ credential=credential,
+ replace=True,
+ public_synonym=True,
+ )
+ select_ai.delete_credential(
+ "MY_PROVIDER_CREDENTIAL",
+ force=True,
+ public_synonym=True,
+ )
+
+The async functions ``async_create_credential()`` and
+``async_delete_credential()`` accept the same ``public_synonym`` option.
+
+Complete sharing sample
+========================
+
+The following samples fetch owner-qualified profiles and vector indexes and
+exercise grant/revoke operations for profiles, vector indexes, teams, and
+credentials. Set ``SELECT_AI_SHARE_GRANTEE`` to a database user or role. The
+objects named by the other optional environment variables must already exist,
+and the scripts should run as their owner.
+
+.. literalinclude:: ../../../samples/sharing.py
+ :language: python
+ :lines: 14-
+
+Representative synchronous output is:
+
+output::
+
+ Profile: APP_OWNER.OCI_AI_PROFILE
+ Profiles: ['APP_OWNER.OCI_AI_PROFILE']
+ Granted profile access to: APP_USER
+ Revoked profile access from: APP_USER
+ Vector index: APP_OWNER.PRODUCT_DOCS
+ Vector indexes: ['APP_OWNER.PRODUCT_DOCS']
+ Granted vector index access to: APP_USER
+ Revoked vector index access from: APP_USER
+ Granted team access to: APP_USER
+ Revoked team access from: APP_USER
+ Granted credential access to: APP_USER
+ Revoked credential access from: APP_USER
+
+The asynchronous sample uses the corresponding async methods:
+
+.. literalinclude:: ../../../samples/async/sharing.py
+ :language: python
+ :lines: 14-
+
+Representative asynchronous output has the same form:
+
+output::
+
+ Profile: APP_OWNER.ASYNC_OCI_AI_PROFILE
+ Profiles: ['APP_OWNER.ASYNC_OCI_AI_PROFILE']
+ Granted profile access to: APP_USER
+ Revoked profile access from: APP_USER
+ Vector index: APP_OWNER.PRODUCT_DOCS
+ Vector indexes: ['APP_OWNER.PRODUCT_DOCS']
+ Granted vector index access to: APP_USER
+ Revoked vector index access from: APP_USER
+ Granted team access to: APP_USER
+ Revoked team access from: APP_USER
+ Granted credential access to: APP_USER
+ Revoked credential access from: APP_USER
diff --git a/doc/source/user_guide/synthetic_data.rst b/doc/source/user_guide/synthetic_data.rst
index 726b13f..d6b6caa 100644
--- a/doc/source/user_guide/synthetic_data.rst
+++ b/doc/source/user_guide/synthetic_data.rst
@@ -146,7 +146,7 @@ output::
.. latex:clearpage::
Single Table Async API
-+++++++++++++++++++++
++++++++++++++++++++++++
.. literalinclude:: ../../../samples/async/profile_gen_single_table_synthetic_data.py
:language: python
diff --git a/doc/source/user_guide/vector_index.rst b/doc/source/user_guide/vector_index.rst
index f90e4d4..689fd60 100644
--- a/doc/source/user_guide/vector_index.rst
+++ b/doc/source/user_guide/vector_index.rst
@@ -40,8 +40,18 @@ The usual lifecycle is:
****************************
.. _vectorindexfig:
-.. figure:: /image/vector_index.png
- :alt: Select AI Vector Index
+
+.. only:: html
+
+ .. figure:: /image/vector_index_object_model.svg
+ :alt: Select AI Vector Index object model
+ :width: 100%
+
+.. only:: latex
+
+ .. figure:: /image/vector_index_object_model.png
+ :alt: Select AI Vector Index object model
+ :width: 100%
.. latex:clearpage::
@@ -155,12 +165,13 @@ Important lifecycle methods:
``replace=True`` and the index already exists, the existing index is
dropped and recreated. Use ``wait_for_completion=True`` when the next
step depends on the initial load being complete.
- * - ``fetch(index_name)``
+ * - ``fetch(index_name, owner=None)``
- Build a ``VectorIndex`` proxy from database metadata, including
attributes and the linked profile when it still exists.
- * - ``list(index_name_pattern=".*")``
- - Iterate over vector indexes visible to the current user. The pattern is
- evaluated with Oracle ``REGEXP_LIKE``.
+ * - ``list(index_name_pattern=".*", owner=None)``
+ - Iterate over vector indexes visible to the current user or owned by the
+ specified schema. The pattern is evaluated with Oracle
+ ``REGEXP_LIKE``.
* - ``set_attribute()`` and ``set_attributes()``
- Update one or more index attributes.
* - ``get_next_refresh_timestamp()``
@@ -214,9 +225,19 @@ Fetch vector index
+++++++++++++++++++++++++++
You can fetch the vector index attributes and associated AI profile using
-the class method ``VectorIndex.fetch(index_name)``. Fetch is useful when the
-index was created earlier or by another process and you want to inspect or
-update it without recreating the original Python object.
+the class method ``VectorIndex.fetch(index_name, owner=None)``. Pass ``owner``
+for an index shared from another schema. The returned object exposes the
+resolved ``owner`` and the owner-qualified ``qualified_name``. Fetch is useful
+when the index was created earlier or by another process and you want to inspect
+or update it without recreating the original Python object.
+
+.. code-block:: python
+
+ vector_index = select_ai.VectorIndex.fetch(
+ "PRODUCT_DOCS",
+ owner="APP_OWNER",
+ )
+ print(vector_index.qualified_name)
.. literalinclude:: ../../../samples/vector_index_fetch.py
:language: python
diff --git a/gcloud/README.md b/gcloud/README.md
index 830cebc..bf005a8 100644
--- a/gcloud/README.md
+++ b/gcloud/README.md
@@ -17,6 +17,15 @@ hosted behind the same A2A endpoint. Consul preserves session and task affinity
when requests reach different gateway instances. Oracle Database capacity and
the configured session TTL remain the limiting factors.
+
+
+The A2A commands are cloud-neutral: `select-ai a2a serve`,
+`select-ai a2a gateway`, and `select-ai a2a worker` can run as processes or
+containers on any cloud platform, a Kubernetes cluster, or self-managed
+infrastructure with the required Oracle and Consul connectivity. The scripts
+in this directory are optional Google Cloud automation for the Cloud Run/GKE
+topologies shown below.
+
## What the A2A client connects to
### Standalone server
@@ -24,12 +33,6 @@ the configured session TTL remain the limiting factors.
The standalone deployment is one Cloud Run A2A service for one configured
Oracle database and one Select AI team.
-```text
-A2A client ── A2A JSON-RPC ──► Cloud Run A2A server ──► Oracle Database
- fixed credentials
- fixed team
-```
-
The service receives its database credentials from Secret Manager. The A2A
client can discover the Agent Card and immediately send a database prompt.
The server supports blocking tasks, task polling, and streaming responses.
@@ -47,15 +50,6 @@ Use [standalone deployment](standalone/README.md) for the deployment details.
The gateway deployment provides one public A2A endpoint for users who choose
the database and Select AI team at runtime.
-```text
-A2A client ── A2A JSON-RPC ──► Cloud Run gateway
- │ A2UI connection form
- ▼
- GKE worker session ──► Oracle Database
- ▲
- │ Consul session/task routing
-```
-
The client first sends a message and receives an A2UI connection form. After
the client submits the DSN, username, password, and team name, the gateway
opens a temporary worker session. Subsequent A2A messages use that session and
diff --git a/samples/README.md b/samples/README.md
index 8e0c860..c083e41 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -21,7 +21,148 @@ export TNS_ADMIN=
Some of the new samples use this optional environment variable:
- `SELECT_AI_PROFILE_NAME` — existing profile for the conversation and
- supervised-team samples.
+ supervised-team, profile lifecycle, translation, and request-attribute
+ samples.
+- `SELECT_AI_SHARE_GRANTEE` — database user or role used by the sharing
+ sample.
+- `SELECT_AI_OWNER`, `SELECT_AI_VECTOR_INDEX_NAME`, `SELECT_AI_TEAM_NAME`, and
+ `SELECT_AI_CREDENTIAL_NAME` — optional names used by the sharing sample.
+
+## Cloud provider profiles
+
+Create a profile and run a test chat against one of the supported cloud AI
+providers:
+
+```bash
+export AWS_ACCESS_KEY_ID=
+export AWS_SECRET_ACCESS_KEY=
+python samples/profile_create_aws.py
+
+export AZURE_API_KEY=
+python samples/profile_create_azure.py
+
+export GOOGLE_API_KEY=
+python samples/profile_create_gcp.py
+```
+
+The scripts create or replace the provider credential and profile, grant the
+database user's HTTP access to the provider endpoint, and print a test chat
+response. See the
+[provider documentation](../doc/source/user_guide/provider.rst#cloud-provider-profile-samples)
+for the provider settings and representative output.
+
+## Supervised agent teams
+
+Create a team with a dedicated supervisor agent, run a prompt through the
+supervisor workflow, and inspect the database-generated supervisor task:
+
+```bash
+python samples/agent/team_supervisor_inspect.py
+python samples/agent/async/team_supervisor_inspect.py
+```
+
+The samples set `AgentAttributes(supervisor=True)` on the coordinating agent
+and pass that agent's name as `TeamAttributes.supervisor_agent`. The database
+populates `supervisor_task` when the team is created.
+
+## Agent definitions and tool inspection
+
+Retrieve a canonical database definition:
+
+```bash
+python samples/agent/get_definition.py
+python samples/agent/async/get_definition.py
+```
+
+Inspect and directly invoke a PL/SQL tool:
+
+```bash
+python samples/agent/tool_run_describe.py
+python samples/agent/async/tool_run_describe.py
+```
+
+The supervised-team samples above also demonstrate
+`Team.describe_team()`/`AsyncTeam.describe_team()` and
+`Team.list_tools()`/`AsyncTeam.list_tools()`.
+
+## Agent execution history
+
+Inspect the latest team execution and its related task and tool history:
+
+```bash
+python samples/agent/history_list.py
+python samples/agent/async/agent_history_list.py
+```
+
+The async sample uses `AsyncTeamHistory`, `AsyncTaskHistory`, and
+`AsyncToolHistory` with async iteration.
+
+## Profile lifecycle controls
+
+Disable and re-enable an existing profile without deleting it:
+
+```bash
+python samples/profile_enable_disable.py
+python samples/async/profile_enable_disable.py
+```
+
+The scripts use `SELECT_AI_PROFILE_NAME` when set; otherwise they use the
+sample profile name `oci_ai_profile`.
+
+## Translation language defaults
+
+Translate text while letting the provider detect the source language:
+
+```bash
+python samples/profile_translate.py
+python samples/async/profile_translate.py
+```
+
+These samples use `SELECT_AI_PROFILE_NAME` when set and pass only the target
+language at the call site. The default profile names are `oci_ai_profile` for
+the synchronous sample and `async_oci_ai_profile` for the asynchronous sample.
+Profile-level language defaults can be configured with `ProfileAttributes` when
+the target is also omitted.
+
+## Request-level profile attribute overrides
+
+Override profile attributes for one request without changing the saved profile:
+
+```bash
+python samples/profile_request_attributes.py
+python samples/async/profile_request_attributes.py
+```
+
+The samples demonstrate `additional_instructions`, integer `seed`, and source
+and target language settings passed through the `attributes` mapping.
+
+## Sharing and ownership
+
+Inspect owner-qualified profiles and vector indexes and grant/revoke access to
+profiles, vector indexes, teams, and credentials:
+
+```bash
+python samples/sharing.py
+python samples/async/sharing.py
+```
+
+Set `SELECT_AI_SHARE_GRANTEE` before running the scripts. The objects must
+already exist, and the scripts should run as their owner. The credential
+creation and deletion samples also create and remove a public synonym.
+
+## Conversation prompt history and tags
+
+Create a conversation, list its stored prompts, delete a prompt, and manage
+conversation tags:
+
+```bash
+python samples/conversation_prompts_tags.py
+python samples/async/conversation_prompts_tags.py
+```
+
+The scripts use `SELECT_AI_PROFILE_NAME` when set; otherwise they use the
+sample profile name `oci_ai_profile`. See the conversation user guide for
+representative output.
## A2A non-blocking task polling
@@ -42,6 +183,21 @@ The sample sends the A2A v0.3 `message/send` request with
`configuration.blocking: false`, prints the returned task ID, and polls
`tasks/get`. Edit `ENDPOINT` or `PROMPT` at the top of the script if needed.
+Representative output is:
+
+```text
+Task 42b...: submitted
+Task 42b...: working
+Task 42b...: completed
+{
+ "id": "42b...",
+ "status": {"state": "completed", "timestamp": "..."},
+ "artifacts": [{"name": "database-agent-result", "parts": ["..."]}]
+}
+```
+
+Task IDs, timestamps, and database answers vary between runs.
+
To compare it with the default blocking behavior, run:
```bash
@@ -52,6 +208,17 @@ This sample intentionally omits `configuration.blocking`. The server waits
for the database work to finish and returns the completed Task in the initial
`message/send` response; no polling is needed.
+Representative output is:
+
+```text
+Task 7e1...: completed
+{
+ "id": "7e1...",
+ "status": {"state": "completed", "timestamp": "..."},
+ "artifacts": [{"name": "database-agent-result", "parts": ["..."]}]
+}
+```
+
## A2A dynamic gateway
The dynamic gateway samples submit the A2UI database connection form, open a
@@ -69,6 +236,10 @@ python samples/a2a/gateway/task_poll.py
See that README for local Consul, worker, and gateway startup instructions.
+The full A2A architecture, protocol details, session lifecycle, and Google
+Cloud deployment explanation are in the
+[A2A user guide](../doc/source/user_guide/a2a.rst).
+
`SELECT_AI_DB_CONNECT_STRING` can be in any one of the following formats
diff --git a/samples/a2a/gateway/README.md b/samples/a2a/gateway/README.md
index c3349da..d5056f4 100644
--- a/samples/a2a/gateway/README.md
+++ b/samples/a2a/gateway/README.md
@@ -17,6 +17,17 @@ send `A2A-Version: 1.0` and use `SendMessage`, `GetTask`, `ListTasks`, and
`CancelTask`; its non-blocking option is `configuration.returnImmediately`.
The gateway accepts both versions, but streaming is disabled in both.
+The sample scripts import `call`, `connect`, `send_prompt`, and
+`print_task_summary` from the adjacent
+[`_common.py`](https://github.com/oracle/python-select-ai/blob/main/samples/a2a/gateway/_common.py)
+file. This is a
+repository-local sample helper, not an additional Python dependency. It sends
+the JSON-RPC requests, performs the A2UI connection-form handshake using the
+environment variables below, and formats the final task result. If you copy a
+script elsewhere, copy
+[`_common.py`](https://github.com/oracle/python-select-ai/blob/main/samples/a2a/gateway/_common.py)
+with it or replace those helpers with your own A2A client code.
+
## Local setup
Install the A2A extra if necessary:
diff --git a/samples/agent/async/team_supervisor_inspect.py b/samples/agent/async/team_supervisor_inspect.py
index 90acc9c..f2d4ba6 100644
--- a/samples/agent/async/team_supervisor_inspect.py
+++ b/samples/agent/async/team_supervisor_inspect.py
@@ -8,8 +8,9 @@
# -----------------------------------------------------------------------------
# agent/async/team_supervisor_inspect.py
#
-# Async version of the supervised team creation and inspection sample.
-# Requires SELECT_AI_PROFILE_NAME to name an existing AI profile.
+# Async version of the supervised team creation, execution, and inspection
+# sample.
+# Uses SELECT_AI_PROFILE_NAME when set; otherwise uses oci_ai_profile.
# -----------------------------------------------------------------------------
import asyncio
@@ -25,11 +26,15 @@
TaskAttributes,
TeamAttributes,
)
+from select_ai.conversation import (
+ AsyncConversation,
+ ConversationAttributes,
+)
user = os.getenv("SELECT_AI_USER")
password = os.getenv("SELECT_AI_PASSWORD")
dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
-profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "LLAMA_4_MAVERICK")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
async def main():
@@ -70,22 +75,47 @@ async def main():
),
)
- await task.create(replace=True)
- await worker.create(replace=True)
- await supervisor.create(replace=True)
- await team.create(replace=True)
+ conversation = AsyncConversation(
+ attributes=ConversationAttributes(
+ title="Supervised team sample",
+ description="Conversation for the supervised team sample",
+ )
+ )
+ created_objects = []
+ conversation_created = False
try:
+ await task.create(replace=True)
+ created_objects.append(task)
+ await worker.create(replace=True)
+ created_objects.append(worker)
+ await supervisor.create(replace=True)
+ created_objects.append(supervisor)
+ await team.create(replace=True)
+ created_objects.append(team)
+
+ await conversation.create()
+ conversation_created = True
+ response = await team.run(
+ prompt="What is the capital of France? Answer in one sentence.",
+ params={"conversation_id": conversation.conversation_id},
+ )
+ if response is None or (
+ isinstance(response, str) and response.startswith("Task failed:")
+ ):
+ raise RuntimeError(f"Supervised team run failed: {response}")
+ print("Team response:", response)
+
fetched = await AsyncTeam.fetch(team.team_name)
print("Supervisor agent:", fetched.attributes.supervisor_agent)
print("Supervisor task:", fetched.attributes.supervisor_task)
print("Team description:", await team.describe_team())
print("Team tools:", await team.list_tools())
finally:
- await team.delete(force=True)
- await supervisor.delete(force=True)
- await worker.delete(force=True)
- await task.delete(force=True)
+ if conversation_created:
+ await conversation.delete(force=True)
+ for obj in reversed(created_objects):
+ await obj.delete(force=True)
asyncio.run(main())
diff --git a/samples/agent/team_supervisor_inspect.py b/samples/agent/team_supervisor_inspect.py
index 5c5cc98..def79d7 100644
--- a/samples/agent/team_supervisor_inspect.py
+++ b/samples/agent/team_supervisor_inspect.py
@@ -8,8 +8,9 @@
# -----------------------------------------------------------------------------
# agent/team_supervisor_inspect.py
#
-# Create a supervised team, then inspect its metadata and available tools.
-# Requires SELECT_AI_PROFILE_NAME to name an existing AI profile.
+# Create a supervised team, run it, then inspect its metadata and available
+# tools.
+# Uses SELECT_AI_PROFILE_NAME when set; otherwise uses oci_ai_profile.
# -----------------------------------------------------------------------------
import os
@@ -24,11 +25,12 @@
Team,
TeamAttributes,
)
+from select_ai.conversation import Conversation, ConversationAttributes
user = os.getenv("SELECT_AI_USER")
password = os.getenv("SELECT_AI_PASSWORD")
dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
-profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "LLAMA_4_MAVERICK")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
suffix = uuid.uuid4().hex.upper()
select_ai.connect(user=user, password=password, dsn=dsn)
@@ -67,19 +69,44 @@
),
)
-task.create(replace=True)
-worker.create(replace=True)
-supervisor.create(replace=True)
-team.create(replace=True)
+conversation = Conversation(
+ attributes=ConversationAttributes(
+ title="Supervised team sample",
+ description="Conversation for the supervised team sample",
+ )
+)
+created_objects = []
+conversation_created = False
try:
+ task.create(replace=True)
+ created_objects.append(task)
+ worker.create(replace=True)
+ created_objects.append(worker)
+ supervisor.create(replace=True)
+ created_objects.append(supervisor)
+ team.create(replace=True)
+ created_objects.append(team)
+
+ conversation.create()
+ conversation_created = True
+ response = team.run(
+ prompt="What is the capital of France? Answer in one sentence.",
+ params={"conversation_id": conversation.conversation_id},
+ )
+ if response is None or (
+ isinstance(response, str) and response.startswith("Task failed:")
+ ):
+ raise RuntimeError(f"Supervised team run failed: {response}")
+ print("Team response:", response)
+
fetched = Team.fetch(team.team_name)
print("Supervisor agent:", fetched.attributes.supervisor_agent)
print("Supervisor task:", fetched.attributes.supervisor_task)
print("Team description:", team.describe_team())
print("Team tools:", team.list_tools())
finally:
- team.delete(force=True)
- supervisor.delete(force=True)
- worker.delete(force=True)
- task.delete(force=True)
+ if conversation_created:
+ conversation.delete(force=True)
+ for obj in reversed(created_objects):
+ obj.delete(force=True)
diff --git a/samples/async/create_ai_credential.py b/samples/async/create_ai_credential.py
index 45af376..3aed3ed 100644
--- a/samples/async/create_ai_credential.py
+++ b/samples/async/create_ai_credential.py
@@ -36,7 +36,9 @@ async def main():
"fingerprint": default_config["fingerprint"],
}
await select_ai.async_create_credential(
- credential=credential, replace=True
+ credential=credential,
+ replace=True,
+ public_synonym=True,
)
print("Created credential: ", credential["credential_name"])
diff --git a/samples/async/delete_ai_credential.py b/samples/async/delete_ai_credential.py
index 94e043e..06816cb 100644
--- a/samples/async/delete_ai_credential.py
+++ b/samples/async/delete_ai_credential.py
@@ -24,7 +24,9 @@
async def main():
await select_ai.async_connect(user=user, password=password, dsn=dsn)
await select_ai.async_delete_credential(
- credential_name="my_oci_ai_profile_key", force=True
+ credential_name="my_oci_ai_profile_key",
+ force=True,
+ public_synonym=True,
)
print("Deleted credential: my_oci_ai_profile_key")
diff --git a/samples/async/profile_enable_disable.py b/samples/async/profile_enable_disable.py
new file mode 100644
index 0000000..30c1fc8
--- /dev/null
+++ b/samples/async/profile_enable_disable.py
@@ -0,0 +1,37 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# async/profile_enable_disable.py
+#
+# Asynchronously disable and re-enable an existing Select AI profile.
+# -----------------------------------------------------------------------------
+
+import asyncio
+import os
+
+import select_ai
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
+
+
+async def main():
+ await select_ai.async_connect(user=user, password=password, dsn=dsn)
+ profile = await select_ai.AsyncProfile(profile_name=profile_name)
+
+ await profile.disable()
+ try:
+ print("Disabled profile:", profile.profile_name)
+ finally:
+ await profile.enable()
+ print("Enabled profile:", profile.profile_name)
+
+
+asyncio.run(main())
diff --git a/samples/async/profile_request_attributes.py b/samples/async/profile_request_attributes.py
new file mode 100644
index 0000000..6a9f959
--- /dev/null
+++ b/samples/async/profile_request_attributes.py
@@ -0,0 +1,45 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# async/profile_request_attributes.py
+#
+# Override profile attributes for one async request without changing the saved
+# profile.
+# -----------------------------------------------------------------------------
+
+import asyncio
+import os
+
+import select_ai
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "async_oci_ai_profile")
+
+
+async def main():
+ await select_ai.async_connect(user=user, password=password, dsn=dsn)
+ async_profile = await select_ai.AsyncProfile(profile_name=profile_name)
+
+ instruction = "Include the exact marker PYSAI_REQUEST_ATTRIBUTES_SAMPLE."
+ request_attributes = {
+ "additional_instructions": instruction,
+ "seed": 42,
+ "source_language": "en",
+ "target_language": "de",
+ }
+
+ prompt = await async_profile.show_prompt(
+ prompt="Tell me about Oracle Cloud Infrastructure.",
+ attributes=request_attributes,
+ )
+ print("Request attributes applied:", instruction in prompt)
+
+
+asyncio.run(main())
diff --git a/samples/async/profile_translate.py b/samples/async/profile_translate.py
index a098fef..c1ca9e0 100644
--- a/samples/async/profile_translate.py
+++ b/samples/async/profile_translate.py
@@ -8,7 +8,7 @@
# -----------------------------------------------------------------------------
# async/profile_translate.py
#
-# Translate text from language to another
+# Translate text while allowing the provider to detect its source language.
# -----------------------------------------------------------------------------
import asyncio
@@ -19,16 +19,16 @@
user = os.getenv("SELECT_AI_USER")
password = os.getenv("SELECT_AI_PASSWORD")
dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "async_oci_ai_profile")
async def main():
await select_ai.async_connect(user=user, password=password, dsn=dsn)
async_profile = await select_ai.AsyncProfile(
- profile_name="async_oci_ai_profile",
+ profile_name=profile_name,
)
response = await async_profile.translate(
text="Thank you",
- source_language="en",
target_language="de",
)
print(response)
diff --git a/samples/async/sharing.py b/samples/async/sharing.py
new file mode 100644
index 0000000..d378d3b
--- /dev/null
+++ b/samples/async/sharing.py
@@ -0,0 +1,90 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# async/sharing.py
+#
+# Inspect owner-qualified objects and grant/revoke access to shared objects
+# using asynchronous APIs. The objects must already exist and the script must
+# run as their owner.
+# -----------------------------------------------------------------------------
+
+import asyncio
+import os
+
+import select_ai
+from select_ai.agent import AsyncTeam
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+owner = os.getenv("SELECT_AI_OWNER") or None
+grantee = os.getenv("SELECT_AI_SHARE_GRANTEE")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "async_oci_ai_profile")
+vector_index_name = os.getenv(
+ "SELECT_AI_VECTOR_INDEX_NAME", "test_vector_index"
+)
+team_name = os.getenv("SELECT_AI_TEAM_NAME", "MOVIE_AGENT_TEAM")
+credential_name = os.getenv(
+ "SELECT_AI_CREDENTIAL_NAME", "my_oci_ai_profile_key"
+)
+
+if not grantee:
+ raise RuntimeError(
+ "Set SELECT_AI_SHARE_GRANTEE to a database user or role"
+ )
+
+
+async def main():
+ await select_ai.async_connect(user=user, password=password, dsn=dsn)
+
+ profile = await select_ai.AsyncProfile.fetch(profile_name, owner=owner)
+ profiles = [
+ item
+ async for item in select_ai.AsyncProfile.list(
+ f"^{profile.profile_name}$",
+ owner=profile.owner,
+ )
+ ]
+ print("Profile:", profile.qualified_name)
+ print("Profiles:", [item.qualified_name for item in profiles])
+ await profile.grant_access(grantee)
+ print("Granted profile access to:", grantee)
+ await profile.revoke_access(grantee)
+ print("Revoked profile access from:", grantee)
+
+ vector_index = await select_ai.AsyncVectorIndex.fetch(
+ vector_index_name,
+ owner=owner,
+ )
+ indexes = [
+ item
+ async for item in select_ai.AsyncVectorIndex.list(
+ f"^{vector_index.index_name}$",
+ owner=vector_index.owner,
+ )
+ ]
+ print("Vector index:", vector_index.qualified_name)
+ print("Vector indexes:", [item.qualified_name for item in indexes])
+ await vector_index.grant_access(grantee)
+ print("Granted vector index access to:", grantee)
+ await vector_index.revoke_access(grantee)
+ print("Revoked vector index access from:", grantee)
+
+ team = await AsyncTeam.fetch(team_name)
+ await team.grant_access(grantee)
+ print("Granted team access to:", grantee)
+ await team.revoke_access(grantee)
+ print("Revoked team access from:", grantee)
+
+ await select_ai.async_grant_credential_access(credential_name, grantee)
+ print("Granted credential access to:", grantee)
+ await select_ai.async_revoke_credential_access(credential_name, grantee)
+ print("Revoked credential access from:", grantee)
+
+
+asyncio.run(main())
diff --git a/samples/conversation_prompts_tags.py b/samples/conversation_prompts_tags.py
index a63bb26..9af7e23 100644
--- a/samples/conversation_prompts_tags.py
+++ b/samples/conversation_prompts_tags.py
@@ -19,6 +19,7 @@
user = os.getenv("SELECT_AI_USER")
password = os.getenv("SELECT_AI_PASSWORD")
dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
select_ai.connect(user=user, password=password, dsn=dsn)
@@ -26,7 +27,7 @@
attributes=select_ai.ConversationAttributes(title="Prompt and tag sample")
)
conversation.create()
-profile = select_ai.Profile(profile_name="oci_ai_profile")
+profile = select_ai.Profile(profile_name=profile_name)
try:
conversation.add_tag("PROJECT", "SELECT_AI")
diff --git a/samples/create_ai_credential.py b/samples/create_ai_credential.py
index d942300..865d48b 100644
--- a/samples/create_ai_credential.py
+++ b/samples/create_ai_credential.py
@@ -34,5 +34,9 @@
"private_key": key_contents,
"fingerprint": default_config["fingerprint"],
}
-select_ai.create_credential(credential=credential, replace=True)
+select_ai.create_credential(
+ credential=credential,
+ replace=True,
+ public_synonym=True,
+)
print("Created credential: ", credential["credential_name"])
diff --git a/samples/delete_ai_credential.py b/samples/delete_ai_credential.py
index 1d54cbb..75e4bb8 100644
--- a/samples/delete_ai_credential.py
+++ b/samples/delete_ai_credential.py
@@ -20,6 +20,8 @@
select_ai.connect(user=user, password=password, dsn=dsn)
select_ai.delete_credential(
- credential_name="my_oci_ai_profile_key", force=True
+ credential_name="my_oci_ai_profile_key",
+ force=True,
+ public_synonym=True,
)
print("Deleted credential: my_oci_ai_profile_key")
diff --git a/samples/profile_enable_disable.py b/samples/profile_enable_disable.py
new file mode 100644
index 0000000..de40b00
--- /dev/null
+++ b/samples/profile_enable_disable.py
@@ -0,0 +1,31 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# profile_enable_disable.py
+#
+# Disable and re-enable an existing Select AI profile.
+# -----------------------------------------------------------------------------
+
+import os
+
+import select_ai
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
+
+select_ai.connect(user=user, password=password, dsn=dsn)
+profile = select_ai.Profile(profile_name=profile_name)
+
+profile.disable()
+try:
+ print("Disabled profile:", profile.profile_name)
+finally:
+ profile.enable()
+ print("Enabled profile:", profile.profile_name)
diff --git a/samples/profile_request_attributes.py b/samples/profile_request_attributes.py
new file mode 100644
index 0000000..95b04a1
--- /dev/null
+++ b/samples/profile_request_attributes.py
@@ -0,0 +1,39 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# profile_request_attributes.py
+#
+# Override profile attributes for one request without changing the saved
+# profile.
+# -----------------------------------------------------------------------------
+
+import os
+
+import select_ai
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
+
+select_ai.connect(user=user, password=password, dsn=dsn)
+profile = select_ai.Profile(profile_name=profile_name)
+
+instruction = "Include the exact marker PYSAI_REQUEST_ATTRIBUTES_SAMPLE."
+request_attributes = {
+ "additional_instructions": instruction,
+ "seed": 42,
+ "source_language": "en",
+ "target_language": "de",
+}
+
+prompt = profile.show_prompt(
+ prompt="Tell me about Oracle Cloud Infrastructure.",
+ attributes=request_attributes,
+)
+print("Request attributes applied:", instruction in prompt)
diff --git a/samples/profile_translate.py b/samples/profile_translate.py
index 8c86ef2..b66a776 100644
--- a/samples/profile_translate.py
+++ b/samples/profile_translate.py
@@ -6,9 +6,9 @@
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
-# profile_summarize_uri.py
+# profile_translate.py
#
-# Summarize content at a certain URI
+# Translate text while allowing the provider to detect its source language.
# -----------------------------------------------------------------------------
import os
@@ -18,10 +18,9 @@
user = os.getenv("SELECT_AI_USER")
password = os.getenv("SELECT_AI_PASSWORD")
dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
select_ai.connect(user=user, password=password, dsn=dsn)
-profile = select_ai.Profile(profile_name="oci_ai_profile")
-response = profile.translate(
- text="Thank you", source_language="en", target_language="de"
-)
+profile = select_ai.Profile(profile_name=profile_name)
+response = profile.translate(text="Thank you", target_language="de")
print(response)
diff --git a/samples/sharing.py b/samples/sharing.py
new file mode 100644
index 0000000..db32a5b
--- /dev/null
+++ b/samples/sharing.py
@@ -0,0 +1,81 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2026, Oracle and/or its affiliates.
+#
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# sharing.py
+#
+# Inspect owner-qualified objects and grant/revoke access to shared objects.
+# The objects must already exist and the script must run as their owner.
+# -----------------------------------------------------------------------------
+
+import os
+
+import select_ai
+from select_ai.agent import Team
+
+user = os.getenv("SELECT_AI_USER")
+password = os.getenv("SELECT_AI_PASSWORD")
+dsn = os.getenv("SELECT_AI_DB_CONNECT_STRING")
+owner = os.getenv("SELECT_AI_OWNER") or None
+grantee = os.getenv("SELECT_AI_SHARE_GRANTEE")
+profile_name = os.getenv("SELECT_AI_PROFILE_NAME", "oci_ai_profile")
+vector_index_name = os.getenv(
+ "SELECT_AI_VECTOR_INDEX_NAME", "test_vector_index"
+)
+team_name = os.getenv("SELECT_AI_TEAM_NAME", "MOVIE_AGENT_TEAM")
+credential_name = os.getenv(
+ "SELECT_AI_CREDENTIAL_NAME", "my_oci_ai_profile_key"
+)
+
+if not grantee:
+ raise RuntimeError(
+ "Set SELECT_AI_SHARE_GRANTEE to a database user or role"
+ )
+
+select_ai.connect(user=user, password=password, dsn=dsn)
+
+profile = select_ai.Profile.fetch(profile_name, owner=owner)
+profiles = list(
+ select_ai.Profile.list(
+ f"^{profile.profile_name}$",
+ owner=profile.owner,
+ )
+)
+print("Profile:", profile.qualified_name)
+print("Profiles:", [item.qualified_name for item in profiles])
+profile.grant_access(grantee)
+print("Granted profile access to:", grantee)
+profile.revoke_access(grantee)
+print("Revoked profile access from:", grantee)
+
+vector_index = select_ai.VectorIndex.fetch(
+ vector_index_name,
+ owner=owner,
+)
+indexes = list(
+ select_ai.VectorIndex.list(
+ f"^{vector_index.index_name}$",
+ owner=vector_index.owner,
+ )
+)
+print("Vector index:", vector_index.qualified_name)
+print("Vector indexes:", [item.qualified_name for item in indexes])
+vector_index.grant_access(grantee)
+print("Granted vector index access to:", grantee)
+vector_index.revoke_access(grantee)
+print("Revoked vector index access from:", grantee)
+
+team = Team.fetch(team_name)
+team.grant_access(grantee)
+print("Granted team access to:", grantee)
+team.revoke_access(grantee)
+print("Revoked team access from:", grantee)
+
+select_ai.grant_credential_access(credential_name, grantee)
+print("Granted credential access to:", grantee)
+select_ai.revoke_credential_access(credential_name, grantee)
+print("Revoked credential access from:", grantee)