Skip to content

Commit 106f7a4

Browse files
committed
Unify A2A standalone and clustered deployments. Add dynamic connection forms and OAuth session isolation
Update Google Cloud scripts, samples, and documentation
1 parent 0878d68 commit 106f7a4

25 files changed

Lines changed: 870 additions & 445 deletions

File tree

‎doc/source/user_guide/a2a.rst‎

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,34 @@ The server prints the discovery URL when it starts:
181181
182182
The exact Uvicorn startup lines vary by version and configuration.
183183

184+
End-user OAuth and session ownership
185+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
186+
187+
By default, ``select-ai a2a serve`` does not require an application OAuth
188+
token. It gives each A2A conversation its own database session. A hosting
189+
platform such as private Cloud Run can still authenticate the calling service,
190+
but the A2A server does not verify which human user owns the conversation.
191+
192+
Add ``--require-oauth`` when verified end-user ownership is required:
193+
194+
.. code-block:: bash
195+
196+
select-ai a2a serve \
197+
--require-oauth \
198+
--team ORACLE_AI_DATABASE_AGENT \
199+
--host 0.0.0.0 \
200+
--port 8000 \
201+
--public-url https://a2a.example.com
202+
203+
Every A2A request must then contain ``Authorization: Bearer ...``. The server
204+
uses both the authenticated owner and the A2A conversation to isolate
205+
sessions, tasks, and conversation state. It accepts an OpenID Connect ID token
206+
or an opaque OAuth 2.0 access token. ID-token ownership remains stable across
207+
refreshes through the ``iss`` and ``sub`` claims; refreshing an opaque access
208+
token starts a new owner and session scope. Tokens are never stored or logged.
209+
210+
The Agent Card advertises bearer security only with ``--require-oauth``.
211+
184212
Agent Card discovery
185213
~~~~~~~~~~~~~~~~~~~~
186214

@@ -384,7 +412,7 @@ The ``-dev`` agent below is intentionally for local development only. It uses
384412
an in-memory, single-node Consul server bound to loopback. For the Google
385413
Cloud deployment, do not install or run a local agent: the deployment creates
386414
Consul in GKE from the
387-
`Consul manifest <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/gke/consul.yaml>`__.
415+
`Consul manifest <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/gke/consul.yaml>`__.
388416

389417
Start the three local components in separate terminals:
390418

@@ -447,14 +475,14 @@ type. It contains only the values not configured on the server:
447475

448476
* - Field
449477
- Purpose
450-
* - ``dsn``
478+
* - ``connection_url``
451479
- Oracle connect descriptor, simplified connect string, or TNS alias
452480
available to the worker.
453481
* - ``username``
454482
- Database user for this session.
455483
* - ``password``
456484
- Database password. The form renders this field as obscured input.
457-
* - ``team_name``
485+
* - ``ai_agent``
458486
- Select AI Agent Team to run in this session.
459487
* - ``submit_database_connection``
460488
- A2UI action that submits exactly the displayed values.
@@ -468,8 +496,9 @@ Use ``--a2ui-form PATH`` to replace the generated form. The file must be a
468496
top-level JSON array of A2UI v0.9 operations using the advertised catalog and
469497
one consistent template ``surfaceId``. It must contain exactly one
470498
``submit_database_connection`` action whose context submits exactly the
471-
missing canonical properties: ``dsn``, ``username``, ``password``, and/or
472-
``team_name``. A collected password must use an obscured ``TextField`` and the
499+
missing canonical properties: ``connection_url``, ``username``, ``password``,
500+
and/or ``ai_agent``. A collected password must use an obscured ``TextField``
501+
and the
473502
template must not contain a password default. The server validates the file at
474503
startup and replaces its template surface ID with a fresh ID every time the
475504
form is displayed.
@@ -643,21 +672,25 @@ Standalone Google Cloud deployment
643672
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
644673

645674
`gcloud/standalone/deploy.sh <https://github.com/oracle/python-select-ai/blob/main/gcloud/standalone/deploy.sh>`__
646-
deploys one private Cloud Run service for one
647-
database and team:
675+
deploys one private Cloud Run service. Select its connection provisioning
676+
explicitly:
648677

649678
.. code-block:: bash
650679
651680
gcloud/standalone/deploy.sh \
652681
--project PROJECT_ID \
653-
--a2a-team ORACLE_AI_DATABASE_AGENT \
682+
--connection-mode dynamic \
654683
--build
655684
656685
The script creates or reuses an Artifact Registry repository, runtime service
657-
account, Secret Manager secrets, and Cloud Run service. On the first run it
658-
prompts for the database user, password, and connect descriptor. It injects
659-
them into the service as ``SELECT_AI_USER``, ``SELECT_AI_PASSWORD``, and
660-
``SELECT_AI_DB_CONNECT_STRING``. The service is private; the script grants
686+
account, any requested Secret Manager secrets, and Cloud Run service. With no
687+
database options, dynamic mode fixes neither the Connection URL nor AI Agent;
688+
its A2UI form requests Connection URL, username, password, and AI Agent. Pass
689+
``--db-dsn-secret`` or ``--a2a-team`` to fix either value independently, or
690+
``--rotate-db-config`` to configure the deployment's Connection URL. Fixed
691+
mode prompts for Connection URL, username, and password, requires
692+
``--a2a-team``, and creates the shared startup pool. The service is private; the
693+
script grants
661694
``run.routes.invoke`` to the active deployment identity and the Gemini
662695
Enterprise Discovery Engine service agent.
663696

@@ -671,13 +704,20 @@ already deployed and only updates Cloud Run configuration or secrets.
671704

672705
Important standalone options include:
673706

707+
* ``--connection-mode``: required ``fixed`` or ``dynamic`` provisioning.
674708
* ``--service``: Cloud Run service name; use a different service for each
675-
fixed team/database deployment.
676-
* ``--a2a-team``: team installed in Oracle Database.
709+
deployment profile.
710+
* ``--a2a-team``: fix the AI Agent in the deployment; required in fixed mode.
711+
* ``--require-oauth``: require a Gemini Enterprise end-user OAuth bearer token
712+
and scope state by verified owner as well as A2A conversation.
677713
* ``--pool-max-size``: maximum Oracle connections per Cloud Run instance.
678-
* ``--max-instances``: Cloud Run instance limit.
679-
* ``--wallet-archive``: upload or replace an Autonomous Database wallet ZIP.
680-
* ``--rotate-db-credentials``: prompt for and rotate the database secrets.
714+
* ``--max-instances``: Cloud Run instance limit; dynamic mode requires one.
715+
* ``--wallet-archive``: upload or replace an Autonomous Database wallet ZIP
716+
in fixed mode.
717+
* ``--db-dsn-secret``: fix the Connection URL using an existing Secret Manager
718+
secret.
719+
* ``--rotate-db-config``: prompt for and create or rotate the deployment's
720+
database values.
681721

682722
For a wallet deployment, ``--wallet-archive`` stores the ZIP and wallet
683723
password in service-specific Secret Manager secrets. Cloud Run mounts the ZIP
@@ -689,61 +729,69 @@ The standalone script prints the deployed Agent Card JSON at the end. The
689729
service is ready for a client when the printed card points to the final Cloud
690730
Run URL and the client can invoke the private service.
691731

692-
Dynamic gateway Google Cloud deployment
732+
The Google Cloud scripts do not require end-user OAuth by default. Cloud Run
733+
IAM authenticates Gemini Enterprise, and ``a2a serve`` separates database
734+
sessions by A2A conversation without asserting a verified human owner. Add
735+
``--require-oauth`` to the deployment command when verified user ownership is
736+
required. Gemini Enterprise must then be configured with end-user OAuth rather
737+
than **Skip & Finish**. It uses ``X-Serverless-Authorization`` for Cloud Run
738+
IAM and sends the user token separately in ``Authorization``.
739+
740+
Clustered Google Cloud deployment
693741
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
694742

695-
`gcloud/gateway/deploy.sh <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/deploy.sh>`__
696-
deploys the gateway topology: Cloud Run for the
697-
public gateway, GKE Autopilot for worker replicas, and Consul for discovery and
743+
`gcloud/cluster/deploy.sh <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/deploy.sh>`__
744+
deploys the clustered topology: Cloud Run for the
745+
public server, GKE Autopilot for worker replicas, and Consul for discovery and
698746
routing.
699747

700748
.. code-block:: bash
701749
702-
gcloud/gateway/deploy.sh \
750+
gcloud/cluster/deploy.sh \
703751
--project PROJECT_ID \
704752
--region us-central1 \
705753
--worker-replicas 3
706754
707755
The script and
708-
`gcloud/gateway/cloudbuild.yaml <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/cloudbuild.yaml>`__
756+
`gcloud/cluster/cloudbuild.yaml <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/cloudbuild.yaml>`__
709757
perform these steps:
710758

711759
1. Enable the required Google Cloud APIs and create or reuse Artifact Registry
712760
and a GKE Autopilot cluster.
713761
2. Configure additive VPC DNS so StatefulSet worker names resolve to current
714762
Pod IPs after a worker is recreated.
715-
3. Deploy the gateway namespace, internal Consul service, and worker service.
763+
3. Deploy the A2A namespace, internal Consul service, and worker service.
716764
4. Build and publish the image with Cloud Build.
717765
5. Deploy the requested worker replicas with ``select-ai a2a worker`` and the
718766
selected session TTL.
719767
6. Deploy Cloud Run with ``select-ai a2a serve --deployment clustered`` and
720768
set ``PUBLIC_URL`` to the final Cloud Run URL.
721769

722-
The Cloud Run gateway uses direct VPC egress to reach the internal Consul
770+
The Cloud Run server uses direct VPC egress to reach the internal Consul
723771
load-balancer address and worker endpoints. The GKE worker service is headless
724772
service discovery, not a public load balancer. Consul selects a healthy worker
725773
when a session opens; the route is then pinned to that worker.
726774

727-
Important gateway options include:
775+
Important clustered options include:
728776

729777
* ``--cluster`` and ``--gke-dns-domain``: GKE Autopilot cluster and immutable
730778
additive DNS domain.
731779
* ``--network`` and ``--subnet``: VPC path used by Cloud Run to reach the
732780
internal worker infrastructure.
733781
* ``--worker-replicas``: number of worker runtimes available for new sessions.
734782
* ``--session-ttl-seconds``: lifetime of temporary database sessions.
735-
* ``--enable-worker-mtls``: create and use gateway-to-worker certificates.
783+
* ``--enable-worker-mtls``: create and use server-to-worker certificates.
736784
* ``--rotate-worker-mtls``: replace the existing test certificates and restart
737785
the worker workload.
738786

739-
The default gateway deployment uses private-VPC HTTP between Cloud Run and
787+
The default clustered deployment uses private-VPC HTTP between Cloud Run and
740788
GKE. With ``--enable-worker-mtls``, the script creates short-lived test PKI
741789
material in Secret Manager, creates the Kubernetes TLS secrets, deploys the
742-
worker StatefulSet variant, and mounts the gateway client certificate into
790+
worker StatefulSet variant, and mounts the server client certificate into
743791
Cloud Run. The worker certificate uses the GKE StatefulSet DNS name, so the
744792
``--gke-dns-domain`` value must remain consistent with the cluster.
745793

746-
The gateway deployment does not upload an Oracle wallet because the dynamic
794+
The clustered deployment does not upload an Oracle wallet because the dynamic
747795
worker session path currently accepts only DSN/user/password. If database mTLS
748796
is required, use the standalone deployment or provide a separate database
749797
connection mechanism to the worker implementation.
@@ -763,15 +811,15 @@ The deployment files are intended to be read together:
763811
- Builds and publishes the standalone container image.
764812
* - `gcloud/standalone/README.md <https://github.com/oracle/python-select-ai/blob/main/gcloud/standalone/README.md>`__
765813
- Documents IAM, secrets, wallet archives, and update behavior.
766-
* - `gcloud/gateway/deploy.sh <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/deploy.sh>`__
767-
- Creates or reuses the gateway GKE/Cloud Run topology and supplies build
814+
* - `gcloud/cluster/deploy.sh <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/deploy.sh>`__
815+
- Creates or reuses the clustered GKE/Cloud Run topology and supplies build
768816
substitutions.
769-
* - `gcloud/gateway/cloudbuild.yaml <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/cloudbuild.yaml>`__
817+
* - `gcloud/cluster/cloudbuild.yaml <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/cloudbuild.yaml>`__
770818
- Builds the image, deploys Consul and workers, and deploys Cloud Run.
771-
* - `gcloud/gateway/gke manifests <https://github.com/oracle/python-select-ai/tree/main/gcloud/gateway/gke>`__
819+
* - `gcloud/cluster/gke manifests <https://github.com/oracle/python-select-ai/tree/main/gcloud/cluster/gke>`__
772820
- Namespace, Consul, headless worker service, and HTTP or mTLS worker
773821
workloads.
774-
* - `gcloud/gateway/README.md <https://github.com/oracle/python-select-ai/blob/main/gcloud/gateway/README.md>`__
822+
* - `gcloud/cluster/README.md <https://github.com/oracle/python-select-ai/blob/main/gcloud/cluster/README.md>`__
775823
- Explains the topology, DNS, mTLS test mode, and operational details.
776824

777825
Troubleshooting and security notes

‎doc/source/user_guide/cli.rst‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ Important options are ``--team`` (required), ``--host``, ``--port``,
257257
wallet for this standalone path. If no password is provided, the command
258258
prompts for it.
259259

260+
By default, ``a2a serve`` does not require an application OAuth token and
261+
separates database sessions by A2A conversation. Add ``--require-oauth`` to
262+
require ``Authorization: Bearer ...`` and scope sessions by authenticated
263+
owner as well as conversation. The Agent Card advertises bearer security only
264+
in that mode.
265+
260266
Dynamic gateway and worker
261267
--------------------------
262268

@@ -297,7 +303,8 @@ The clustered serve options are ``--public-url`` (or ``PUBLIC_URL``),
297303
``SESSION_TTL_SECONDS``). ``--dsn``, ``--user``, ``--password``, and ``--team``
298304
fix any supplied connection values; the generated A2UI form contains only the
299305
missing values. ``--a2ui-form`` loads a custom A2UI JSON form that must submit
300-
exactly those missing values. The optional
306+
exactly those missing values. ``--require-oauth`` has the same ownership
307+
behavior in standalone and clustered deployments. The optional
301308
``--worker-tls-ca-file``, ``--worker-tls-cert-file``, and
302309
``--worker-tls-key-file`` options configure the gateway's client side of
303310
gateway-to-worker mTLS. These TLS settings protect the internal HTTP hop and

‎docker/a2a-entrypoint.sh‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,21 @@ if [ -f "$wallet_archive" ]; then
2828
export SELECT_AI_WALLET_LOCATION="$(dirname "$wallet_file")"
2929
fi
3030

31-
: "${SELECT_AI_A2A_TEAM:?SELECT_AI_A2A_TEAM is required}"
3231
: "${PUBLIC_URL:?PUBLIC_URL is required}"
3332
: "${SELECT_AI_POOL_MAX_SIZE:=10}"
3433

35-
exec select-ai a2a serve \
36-
--deployment standalone \
37-
--team "$SELECT_AI_A2A_TEAM" \
38-
--host 0.0.0.0 \
39-
--port "${PORT:-8080}" \
40-
--pool-max-size "$SELECT_AI_POOL_MAX_SIZE" \
34+
serve_args=(
35+
--deployment standalone
36+
--host 0.0.0.0
37+
--port "${PORT:-8080}"
38+
--pool-max-size "$SELECT_AI_POOL_MAX_SIZE"
4139
--public-url "$PUBLIC_URL"
40+
)
41+
if [ -n "${SELECT_AI_A2A_TEAM:-}" ]; then
42+
serve_args+=(--team "$SELECT_AI_A2A_TEAM")
43+
fi
44+
if [ "${SELECT_AI_A2A_REQUIRE_OAUTH:-false}" = "true" ]; then
45+
serve_args+=(--require-oauth)
46+
fi
47+
48+
exec select-ai a2a serve "${serve_args[@]}"

0 commit comments

Comments
 (0)