From 25062aa1a8f45971f8fc3a8fb28d3648b1b78f10 Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Fri, 28 Aug 2026 13:45:18 -0500 Subject: [PATCH 01/13] Add SandboxAQ agent --- agents/sandboxaq/README.md | 28 +++++++ agents/sandboxaq/agent.yaml | 36 +++++++++ agents/sandboxaq/metadata.yaml | 17 +++++ .../tools/sandboxaq-model/Dockerfile | 11 +++ .../tools/sandboxaq-model/run_action.py | 76 +++++++++++++++++++ .../sandboxaq/tools/sandboxaq-model/tool.yaml | 50 ++++++++++++ 6 files changed, 218 insertions(+) create mode 100644 agents/sandboxaq/README.md create mode 100644 agents/sandboxaq/agent.yaml create mode 100644 agents/sandboxaq/metadata.yaml create mode 100644 agents/sandboxaq/tools/sandboxaq-model/Dockerfile create mode 100644 agents/sandboxaq/tools/sandboxaq-model/run_action.py create mode 100644 agents/sandboxaq/tools/sandboxaq-model/tool.yaml diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md new file mode 100644 index 00000000..13775853 --- /dev/null +++ b/agents/sandboxaq/README.md @@ -0,0 +1,28 @@ +# SandboxAQ Foundry Model Agent + +This agent invokes a SandboxAQ model that your organization has subscribed to +through Azure AI Foundry. It uses a Discovery-managed tool to call the +OpenAI-compatible Foundry chat-completions endpoint. + +## Configuration + +Configure these values on the deployed tool; do not commit them to the catalog: + +| Environment variable | Required | Description | +|---|---:|---| +| `FOUNDRY_ENDPOINT` | Yes | Azure AI Foundry endpoint, including the `/openai` API base | +| `FOUNDRY_API_KEY` | Yes | Secret for the subscribed deployment | +| `FOUNDRY_DEPLOYMENT` | Yes | SandboxAQ model deployment name | +| `FOUNDRY_API_VERSION` | No | API version; defaults to `2024-10-21` | + +The endpoint must be reachable from the Discovery tool container. Use the +Foundry deployment's supported API version and authentication method if it +differs from this default. + +## Example requests + +- "Run the SandboxAQ model on this prompt and return the result as JSON." +- "Summarize this HR policy using the subscribed SandboxAQ deployment." + +For sensitive HR data, apply your organization's privacy, access-control, and +human-review requirements. This agent does not make employment decisions. diff --git a/agents/sandboxaq/agent.yaml b/agents/sandboxaq/agent.yaml new file mode 100644 index 00000000..9df5d1c0 --- /dev/null +++ b/agents/sandboxaq/agent.yaml @@ -0,0 +1,36 @@ +kind: prompt +name: sandboxaq +displayName: SandboxAQ Foundry Model Agent +description: > + Invokes subscribed SandboxAQ models through an Azure AI Foundry endpoint. +model: + id: '{{CHAT-MODEL}}' + options: + temperature: 0 + maxOutputTokens: 4096 +instructions: |- + You are the SandboxAQ Foundry Model Agent. + + Use the SandboxAQ model endpoint through the `invoke_model` tool when the + user asks for model inference. Do not claim that a model was invoked unless + the tool returns a successful response. + + Keep user inputs and model outputs clearly separated. Preserve structured + output when the endpoint returns JSON. Never expose endpoint URLs, API keys, + authorization headers, or other deployment secrets. + + For HR or other sensitive business data: + - Follow the user's organization's access and data-handling policies. + - Do not infer protected attributes or make employment decisions. + - State when the model response is uncertain or requires human review. + - Return only the minimum sensitive information needed for the request. + + If the request is missing the information required by the endpoint, ask one + focused clarifying question. +discoveryExtensions: + humanInTheLoop: Disabled + tools: + - toolId: '{{SANDBOAQ_MODEL_TOOL_ID}}' + confirmation: Disabled + disableDataHandlingTools: false + disableDiscoveryInjectedTools: false diff --git a/agents/sandboxaq/metadata.yaml b/agents/sandboxaq/metadata.yaml new file mode 100644 index 00000000..3b7686e6 --- /dev/null +++ b/agents/sandboxaq/metadata.yaml @@ -0,0 +1,17 @@ +name: sandboxaq +type: agent +version: 1.0.0 +associated_tools: + - agents/sandboxaq/tools/sandboxaq-model +publisher: + name: SandboxAQ + contact: support@sandboxaq.com + support_url: https://www.sandboxaq.com/contact + party: 3p +description: > + Invokes SandboxAQ models subscribed through Azure AI Foundry for secure, + structured inference workflows. +tags: + - sandboxaq + - foundry + - model-inference diff --git a/agents/sandboxaq/tools/sandboxaq-model/Dockerfile b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile new file mode 100644 index 00000000..6556e50d --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/azurelinux/base/python:3.12 + +WORKDIR /app +RUN tdnf -y update && \ + tdnf -y install ca-certificates && \ + update-ca-trust && \ + tdnf clean all +RUN pip3 install --no-cache-dir requests + +COPY run_action.py /app/run_action.py +ENTRYPOINT ["python3", "/app/run_action.py"] diff --git a/agents/sandboxaq/tools/sandboxaq-model/run_action.py b/agents/sandboxaq/tools/sandboxaq-model/run_action.py new file mode 100644 index 00000000..6a972fad --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/run_action.py @@ -0,0 +1,76 @@ +import argparse +import json +import os +import sys + +import requests + + +def required_env(name): + value = os.environ.get(name) + if not value: + raise RuntimeError(f"Required environment variable is not set: {name}") + return value + + +def invoke_model(prompt, system_prompt, temperature, max_tokens): + endpoint = required_env("FOUNDRY_ENDPOINT").rstrip("/") + deployment = required_env("FOUNDRY_DEPLOYMENT") + api_key = required_env("FOUNDRY_API_KEY") + api_version = os.environ.get("FOUNDRY_API_VERSION", "2024-10-21") + url = ( + f"{endpoint}/deployments/{deployment}/chat/completions" + f"?api-version={api_version}" + ) + messages = [] + if system_prompt: + messages.append({"role": "system", "content": system_prompt}) + messages.append({"role": "user", "content": prompt}) + response = requests.post( + url, + headers={"api-key": api_key, "Content-Type": "application/json"}, + json={ + "messages": messages, + "temperature": temperature, + "max_tokens": max_tokens, + }, + timeout=300, + ) + response.raise_for_status() + payload = response.json() + choices = payload.get("choices") + if not choices: + raise RuntimeError("Foundry response did not contain any choices") + return { + "model": payload.get("model", deployment), + "content": choices[0].get("message", {}).get("content", ""), + "usage": payload.get("usage"), + "raw_response": payload, + } + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--action", required=True) + parser.add_argument("--prompt", required=True) + parser.add_argument("--system", default="") + parser.add_argument("--temperature", default="0") + parser.add_argument("--max-tokens", default="4096") + args = parser.parse_args() + if args.action != "invoke_model": + raise ValueError(f"Unsupported action: {args.action}") + result = invoke_model( + args.prompt, + args.system, + float(args.temperature), + int(args.max_tokens), + ) + print(json.dumps(result)) + + +if __name__ == "__main__": + try: + main() + except (RuntimeError, ValueError, requests.RequestException) as error: + print(json.dumps({"error": str(error)}), file=sys.stderr) + sys.exit(1) diff --git a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml new file mode 100644 index 00000000..649b676b --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml @@ -0,0 +1,50 @@ +name: sandboxaq-model +description: > + Invokes a subscribed SandboxAQ model through its Azure AI Foundry + OpenAI-compatible chat-completions endpoint. +version: 1.0.0 +license: MIT +category: "Model Inference" +infra: + - name: worker + infra_type: container + image: + acr: "{name}/sandboxaq-model:latest" + compute: + pool_type: static + pool_size: 1 + min_resources: + cpu: 1 + ram: "2Gi" + storage: "8Gi" + gpu: 0 + max_resources: + cpu: 2 + ram: "4Gi" + storage: "16Gi" + gpu: 0 +actions: + - name: invoke_model + description: > + Send a prompt to the configured SandboxAQ Azure AI Foundry deployment. + The endpoint, deployment name, and credential are supplied as runtime + environment variables. + infra_node: worker + command: "python3 /app/run_action.py --action invoke_model --prompt '{{prompt}}' --system '{{system_prompt}}' --temperature '{{temperature}}' --max-tokens '{{max_tokens}}'" + input_schema: + type: object + properties: + prompt: + type: string + description: User prompt to send to the SandboxAQ model. + system_prompt: + type: string + description: Optional system instruction for this invocation. + temperature: + type: number + description: Sampling temperature. Defaults to 0. + max_tokens: + type: integer + description: Maximum completion tokens. Defaults to 4096. + required: + - prompt From 215031b9ecdcf7b3afd2025a609145c1b8daa59a Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Fri, 28 Aug 2026 14:20:54 -0500 Subject: [PATCH 02/13] Fix tool binding placeholder and shell-quoted prompt handling Two defects in the initial SandboxAQ agent: 1. The toolId placeholder was `{{SANDBOAQ_MODEL_TOOL_ID}}` -- misspelled and not in the catalog's `{{ToolId}}` camelCase convention, so it would not resolve at provisioning time. Now `{{sandboxaqModelToolId}}`. 2. The tool declared an `actions:` entry whose command interpolated the user's prompt into a shell string (`--prompt '{{prompt}}'`). Because the runtime substitutes textually, any prompt containing an apostrophe was mangled -- and no quoting style fixes this in general: single quotes break on `'`, double quotes break on `"` and expand `$(...)`. Switch the tool to the `code_environments` pattern used by the majority of the catalog. The agent now writes a Python script that imports `sandboxaq_utils` and calls `invoke_model(...)`, so prompt text travels as a Python string literal and never reaches a shell. - run_action.py -> sandboxaq_utils.py, an importable module exposing `invoke_model()` and `save_json()` instead of an argparse entrypoint. - Dockerfile ships the module to /app on PYTHONPATH, creates /input /output /workdir, and drops the ENTRYPOINT in favour of the code environment. - tool.yaml replaces `actions:` with a `python3` code environment. - agent.yaml instructions document the script template and the installed package set. - Normalize language to `python3` (required for Foundry agent binding). Verified: prompts containing apostrophes, double quotes, `$(whoami)`, and newlines now reach the endpoint byte-identically; the documented script template executes as written; the image builds and the module imports from PYTHONPATH inside the container. --- agents/sandboxaq/README.md | 12 ++++ agents/sandboxaq/agent.yaml | 60 ++++++++++++++--- .../tools/sandboxaq-model/Dockerfile | 14 +++- .../{run_action.py => sandboxaq_utils.py} | 67 ++++++++++--------- .../sandboxaq/tools/sandboxaq-model/tool.yaml | 30 ++------- 5 files changed, 117 insertions(+), 66 deletions(-) rename agents/sandboxaq/tools/sandboxaq-model/{run_action.py => sandboxaq_utils.py} (51%) diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md index 13775853..1a68dbb9 100644 --- a/agents/sandboxaq/README.md +++ b/agents/sandboxaq/README.md @@ -4,6 +4,18 @@ This agent invokes a SandboxAQ model that your organization has subscribed to through Azure AI Foundry. It uses a Discovery-managed tool to call the OpenAI-compatible Foundry chat-completions endpoint. +## How it works + +The tool exposes a Python code environment rather than a fixed action. The +agent writes a script that imports `sandboxaq_utils` and calls +`invoke_model(prompt, system_prompt=..., temperature=..., max_tokens=...)`, +which returns a dict with `model`, `content`, `usage`, and `raw_response`. +Results are written to `/output/final_results.json`. + +Prompts therefore travel as Python string literals and are never interpolated +into a shell command, so text containing apostrophes, double quotes, `$`, or +newlines is passed through to the deployment unmodified. + ## Configuration Configure these values on the deployed tool; do not commit them to the catalog: diff --git a/agents/sandboxaq/agent.yaml b/agents/sandboxaq/agent.yaml index 9df5d1c0..763b82cf 100644 --- a/agents/sandboxaq/agent.yaml +++ b/agents/sandboxaq/agent.yaml @@ -9,28 +9,66 @@ model: temperature: 0 maxOutputTokens: 4096 instructions: |- - You are the SandboxAQ Foundry Model Agent. + You are the SandboxAQ Foundry Model Agent. You reach the subscribed + SandboxAQ deployment by writing Python scripts that run in the tool's code + environment. - Use the SandboxAQ model endpoint through the `invoke_model` tool when the - user asks for model inference. Do not claim that a model was invoked unless - the tool returns a successful response. + # INSTALLED PYTHON PACKAGES + `requests`, `json` (stdlib), and `sandboxaq_utils` (custom module). + **NOT available:** pandas, numpy, rdkit, matplotlib. Do NOT import these. - Keep user inputs and model outputs clearly separated. Preserve structured - output when the endpoint returns JSON. Never expose endpoint URLs, API keys, - authorization headers, or other deployment secrets. + **ALWAYS use `from sandboxaq_utils import invoke_model`** -- this is the + ONLY supported way to call the deployment. Never build the request URL, + headers, or credential yourself, and never invoke the model through a shell + command; passing prompt text through a shell corrupts any prompt containing + quotes or `$`. + + ## CRITICAL RULES + + 1. Put the prompt in a Python variable, not in a shell command. Triple-quoted + string literals handle apostrophes, quotes, and newlines correctly. + 2. Write results to `/output/final_results.json` with `save_json()`. + 3. Do not claim a model was invoked unless the script completed and returned + a response. + 4. Keep user inputs and model outputs clearly separated in your report. + Preserve structured output when the endpoint returns JSON. + 5. Never expose endpoint URLs, API keys, authorization headers, or other + deployment secrets -- not in scripts, logs, or your reply. + 6. If the request is missing information the endpoint requires, ask one + focused clarifying question before running anything. + + ### Script template + + ```python + from sandboxaq_utils import invoke_model, save_json + + prompt = """Summarize the attached policy in five bullet points.""" + + result = invoke_model( + prompt, + system_prompt="You are a concise policy analyst.", + temperature=0, + max_tokens=4096, + ) + + print(result["content"]) + save_json(result, "/output/final_results.json") + ``` + + `invoke_model` returns a dict with `model`, `content`, `usage`, and + `raw_response`. It raises `RuntimeError` when the deployment is not + configured and `requests.RequestException` on HTTP failures -- report those + errors verbatim rather than retrying blindly. For HR or other sensitive business data: - Follow the user's organization's access and data-handling policies. - Do not infer protected attributes or make employment decisions. - State when the model response is uncertain or requires human review. - Return only the minimum sensitive information needed for the request. - - If the request is missing the information required by the endpoint, ask one - focused clarifying question. discoveryExtensions: humanInTheLoop: Disabled tools: - - toolId: '{{SANDBOAQ_MODEL_TOOL_ID}}' + - toolId: '{{sandboxaqModelToolId}}' confirmation: Disabled disableDataHandlingTools: false disableDiscoveryInjectedTools: false diff --git a/agents/sandboxaq/tools/sandboxaq-model/Dockerfile b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile index 6556e50d..5bd04dba 100644 --- a/agents/sandboxaq/tools/sandboxaq-model/Dockerfile +++ b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile @@ -1,11 +1,21 @@ FROM mcr.microsoft.com/azurelinux/base/python:3.12 WORKDIR /app + +# System update + TLS certificates (MUST be first RUN for layer sharing) RUN tdnf -y update && \ tdnf -y install ca-certificates && \ update-ca-trust && \ tdnf clean all RUN pip3 install --no-cache-dir requests -COPY run_action.py /app/run_action.py -ENTRYPOINT ["python3", "/app/run_action.py"] +# Create working directories +RUN mkdir -p /input /output /workdir + +# /app: safe from volume mounts, reachable via PYTHONPATH +COPY sandboxaq_utils.py /app/ + +WORKDIR /workdir +ENV PYTHONPATH="/app" + +CMD ["/bin/bash"] diff --git a/agents/sandboxaq/tools/sandboxaq-model/run_action.py b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py similarity index 51% rename from agents/sandboxaq/tools/sandboxaq-model/run_action.py rename to agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py index 6a972fad..be6905df 100644 --- a/agents/sandboxaq/tools/sandboxaq-model/run_action.py +++ b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py @@ -1,10 +1,22 @@ -import argparse +"""Helpers for calling a subscribed SandboxAQ model through Azure AI Foundry. + +Agent scripts import from this module rather than shelling out, so prompt text +is never interpreted by a shell: + + from sandboxaq_utils import invoke_model, save_json + + result = invoke_model("Summarize this policy") + save_json(result, "/output/final_results.json") +""" + import json import os -import sys import requests +DEFAULT_API_VERSION = "2024-10-21" +DEFAULT_TIMEOUT_SECONDS = 300 + def required_env(name): value = os.environ.get(name) @@ -13,11 +25,23 @@ def required_env(name): return value -def invoke_model(prompt, system_prompt, temperature, max_tokens): +def invoke_model( + prompt, + system_prompt="", + temperature=0, + max_tokens=4096, + timeout=DEFAULT_TIMEOUT_SECONDS, +): + """Send a prompt to the configured SandboxAQ deployment. + + Returns a dict with `model`, `content`, `usage`, and `raw_response`. + Raises RuntimeError if the deployment is not configured or returns no + choices, and requests.RequestException on transport/HTTP failures. + """ endpoint = required_env("FOUNDRY_ENDPOINT").rstrip("/") deployment = required_env("FOUNDRY_DEPLOYMENT") api_key = required_env("FOUNDRY_API_KEY") - api_version = os.environ.get("FOUNDRY_API_VERSION", "2024-10-21") + api_version = os.environ.get("FOUNDRY_API_VERSION", DEFAULT_API_VERSION) url = ( f"{endpoint}/deployments/{deployment}/chat/completions" f"?api-version={api_version}" @@ -34,7 +58,7 @@ def invoke_model(prompt, system_prompt, temperature, max_tokens): "temperature": temperature, "max_tokens": max_tokens, }, - timeout=300, + timeout=timeout, ) response.raise_for_status() payload = response.json() @@ -49,28 +73,11 @@ def invoke_model(prompt, system_prompt, temperature, max_tokens): } -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--action", required=True) - parser.add_argument("--prompt", required=True) - parser.add_argument("--system", default="") - parser.add_argument("--temperature", default="0") - parser.add_argument("--max-tokens", default="4096") - args = parser.parse_args() - if args.action != "invoke_model": - raise ValueError(f"Unsupported action: {args.action}") - result = invoke_model( - args.prompt, - args.system, - float(args.temperature), - int(args.max_tokens), - ) - print(json.dumps(result)) - - -if __name__ == "__main__": - try: - main() - except (RuntimeError, ValueError, requests.RequestException) as error: - print(json.dumps({"error": str(error)}), file=sys.stderr) - sys.exit(1) +def save_json(data, path): + """Write `data` to `path` as UTF-8 JSON, creating parent directories.""" + parent = os.path.dirname(path) + if parent: + os.makedirs(parent, exist_ok=True) + with open(path, "w", encoding="utf-8") as handle: + json.dump(data, handle, indent=2, ensure_ascii=False) + return path diff --git a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml index 649b676b..8700a4f8 100644 --- a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml +++ b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml @@ -23,28 +23,12 @@ infra: ram: "4Gi" storage: "16Gi" gpu: 0 -actions: - - name: invoke_model +code_environments: + - language: python3 + command: "python3 -u \"/{{scriptName}}\"" description: > - Send a prompt to the configured SandboxAQ Azure AI Foundry deployment. - The endpoint, deployment name, and credential are supplied as runtime - environment variables. + Python code environment on the SandboxAQ model container image. Scripts + import `sandboxaq_utils` to reach the configured Azure AI Foundry + deployment; the endpoint, deployment name, and credential are supplied + as runtime environment variables. infra_node: worker - command: "python3 /app/run_action.py --action invoke_model --prompt '{{prompt}}' --system '{{system_prompt}}' --temperature '{{temperature}}' --max-tokens '{{max_tokens}}'" - input_schema: - type: object - properties: - prompt: - type: string - description: User prompt to send to the SandboxAQ model. - system_prompt: - type: string - description: Optional system instruction for this invocation. - temperature: - type: number - description: Sampling temperature. Defaults to 0. - max_tokens: - type: integer - description: Maximum completion tokens. Defaults to 4096. - required: - - prompt From 7f559087039850f015b45e382e760b7dfa4a2a7c Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Fri, 28 Aug 2026 14:31:25 -0500 Subject: [PATCH 03/13] Align agent with catalog conventions and clear PR validation Addresses all 7 findings from the pr-review bot (SCH-037, DOC-003, DOC-004, DOC-005, DOC-102, DOC-104, DOC-105) plus conformance gaps found by comparing against the other 46 catalog agents. tool.yaml: - infra[].image.acr uses the deployer placeholder form `{name}.azurecr.io/sandboxaq-model:latest`. The bare `{name}/...` form renders without a registry domain (SCH-037); 39/44 catalog tools use the .azurecr.io form, and the 4 exceptions are all `actions:`-based EDA tools. - Add `recommended_sku: Standard_D4s_v6` -- this was the only tool of 44 without one. - Match the compute profile shared by all 11 API-calling peer tools (chembl, pubchem, zinc, pubmed, clinical-trials, ...): ram 4Gi->8Gi and storage 8Gi->32Gi, plus `infiniband: false`. agent.yaml: - Add `topP: 0` to model.options; 35/47 agents set it and the deploy path reads model.options.topP. README.md: - Restructure to the section layout shared by 46/47 agents: Overview, Prerequisites, Build Docker Image, File Structure, Key Configuration Details, Usage, Architecture, Configuration, Support, Tools, Known Limitations, Contributing. It was previously the shortest README in the catalog and was missing six required sections. Verified with .github/scripts/validate_pr.py (the script CI runs) against the current schemas: all checks pass. Confirmed the validator is live via a negative control (an invalid publisher.party is correctly rejected). --- agents/sandboxaq/README.md | 152 ++++++++++++++++-- agents/sandboxaq/agent.yaml | 1 + .../sandboxaq/tools/sandboxaq-model/tool.yaml | 17 +- 3 files changed, 152 insertions(+), 18 deletions(-) diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md index 1a68dbb9..d65f9a33 100644 --- a/agents/sandboxaq/README.md +++ b/agents/sandboxaq/README.md @@ -1,23 +1,127 @@ -# SandboxAQ Foundry Model Agent +# SandboxAQ Foundry Model Agent Deployment Guide + +This guide provides step-by-step instructions for deploying the SandboxAQ +Foundry Model tool and its associated agent to the Microsoft Discovery +platform. + +## Overview This agent invokes a SandboxAQ model that your organization has subscribed to -through Azure AI Foundry. It uses a Discovery-managed tool to call the -OpenAI-compatible Foundry chat-completions endpoint. +through Azure AI Foundry, calling the OpenAI-compatible chat-completions +endpoint from a Discovery-managed tool container. This deployment includes: + +- **Dockerfile**: Used for creation of the SandboxAQ model tool container image +- **Tool Definition**: Configuration for the `sandboxaq-model` tool +- **Agent Definition**: AI agent configuration for the SandboxAQ agent + +## Prerequisites + +Before starting the deployment, ensure you have: + +1. Access to Microsoft Discovery platform +2. An active SandboxAQ model subscription deployed in Azure AI Foundry +3. Azure Container Registry (ACR) with appropriate permissions +4. Docker installed locally for image building +5. Azure CLI or PowerShell for resource management + +## Build Docker Image + +### Step 1: Build and Publish Docker Image + +1. **Build the image** from this tool directory: + + ```bash + docker build -t sandboxaq-model:latest . + ``` + +2. **Tag the image** for your Azure Container Registry: + + ```bash + docker tag sandboxaq-model:latest mycontainerregistry.azurecr.io/sandboxaq-model:latest + ``` + + > Replace `mycontainerregistry` with your actual ACR name + +3. **Login to Azure Container Registry**: + + ```bash + az acr login --name mycontainerregistry + ``` + +4. **Push the image** to your container registry: + + ```bash + docker push mycontainerregistry.azurecr.io/sandboxaq-model:latest + ``` + +## File Structure + +```text +sandboxaq/ +├── agent.yaml # Agent configuration (YAML) +├── metadata.yaml # Catalog metadata +├── tools/ +│ └── sandboxaq-model/ +│ ├── Dockerfile # Container image definition +│ ├── tool.yaml # Tool configuration (YAML) +│ └── sandboxaq_utils.py # Foundry client helper module +└── README.md # This deployment guide +``` + +## Key Configuration Details -## How it works +### Agent Capabilities + +The SandboxAQ agent provides: + +- **Model Invocation**: Sends prompts to the subscribed SandboxAQ deployment +- **Structured Output**: Preserves JSON returned by the endpoint and writes + results to `/output/final_results.json` +- **Sensitive-Data Handling**: Applies access, privacy, and human-review + guidance when working with HR or other confidential business data + +## Usage + +### Basic Queries + +| Prompt | Description | +|--------|-------------| +| "Run the SandboxAQ model on this prompt and return the result as JSON." | Direct invocation | +| "Summarize this HR policy using the subscribed SandboxAQ deployment." | Summarization | +| "Ask the SandboxAQ model to classify these support tickets." | Classification | + +### Advanced Queries + +| Prompt | Description | +|--------|-------------| +| "Run this prompt at temperature 0.7 and cap the output at 1000 tokens." | Tuned sampling parameters | +| "Use a system prompt of 'You are a concise policy analyst' for this request." | Custom system instruction | +| "Invoke the model on each of these five prompts and collect the results." | Batch invocation in one script | + +## Architecture + +This agent operates as a `kind: prompt` agent within Discovery Studio. + + User Input → SandboxAQ Agent (LLM) → sandboxaq-model Tool (Container) → Azure AI Foundry → Results + +- **Model:** Configured via the `{{CHAT-MODEL}}` parameter at deploy time +- **Tool:** `sandboxaq-model` container exposing a `python3` code environment The tool exposes a Python code environment rather than a fixed action. The agent writes a script that imports `sandboxaq_utils` and calls `invoke_model(prompt, system_prompt=..., temperature=..., max_tokens=...)`, which returns a dict with `model`, `content`, `usage`, and `raw_response`. -Results are written to `/output/final_results.json`. Prompts therefore travel as Python string literals and are never interpolated into a shell command, so text containing apostrophes, double quotes, `$`, or -newlines is passed through to the deployment unmodified. +newlines reaches the deployment unmodified. ## Configuration +| Parameter | Description | Example | +|---|---|---| +| `{{CHAT-MODEL}}` | Azure AI Foundry model deployment name for the agent | `gpt-4o` | + Configure these values on the deployed tool; do not commit them to the catalog: | Environment variable | Required | Description | @@ -31,10 +135,36 @@ The endpoint must be reachable from the Discovery tool container. Use the Foundry deployment's supported API version and authentication method if it differs from this default. -## Example requests +## Support + +For issues or questions with this agent, contact SandboxAQ: + + +SandboxAQ contact: support@sandboxaq.com + +For platform issues, open a GitHub issue: + + +## Tools + +| Tool | Path | Description | +|---|---|---| +| `sandboxaq-model` | `tools/sandboxaq-model/` | Invokes a subscribed SandboxAQ model through its Azure AI Foundry OpenAI-compatible chat-completions endpoint. | + +## Known Limitations + +- Requires an existing SandboxAQ subscription in Azure AI Foundry; the agent + cannot provision or discover deployments on your behalf. +- Supports the chat-completions API surface only. Embeddings, batch, and + streaming endpoints are not exposed. +- Credentials are supplied as environment variables on the deployed tool, so + rotating a key requires updating the tool configuration. +- For sensitive HR data, apply your organization's privacy, access-control, + and human-review requirements. This agent does not make employment + decisions. -- "Run the SandboxAQ model on this prompt and return the result as JSON." -- "Summarize this HR policy using the subscribed SandboxAQ deployment." +## Contributing -For sensitive HR data, apply your organization's privacy, access-control, and -human-review requirements. This agent does not make employment decisions. +This project welcomes contributions and suggestions. Please see the +repository's top-level [CONTRIBUTING guidelines](https://github.com/microsoft/discovery/blob/main/CONTRIBUTING.md) +for details on how to contribute. diff --git a/agents/sandboxaq/agent.yaml b/agents/sandboxaq/agent.yaml index 763b82cf..19261d88 100644 --- a/agents/sandboxaq/agent.yaml +++ b/agents/sandboxaq/agent.yaml @@ -7,6 +7,7 @@ model: id: '{{CHAT-MODEL}}' options: temperature: 0 + topP: 0 maxOutputTokens: 4096 instructions: |- You are the SandboxAQ Foundry Model Agent. You reach the subscribed diff --git a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml index 8700a4f8..a789fe10 100644 --- a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml +++ b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml @@ -9,20 +9,23 @@ infra: - name: worker infra_type: container image: - acr: "{name}/sandboxaq-model:latest" + acr: "{name}.azurecr.io/sandboxaq-model:latest" compute: - pool_type: static - pool_size: 1 min_resources: cpu: 1 - ram: "2Gi" - storage: "8Gi" + ram: 4Gi + storage: 8Gi gpu: 0 max_resources: cpu: 2 - ram: "4Gi" - storage: "16Gi" + ram: 8Gi + storage: 32Gi gpu: 0 + infiniband: false + recommended_sku: + - Standard_D4s_v6 + pool_type: static + pool_size: 1 code_environments: - language: python3 command: "python3 -u \"/{{scriptName}}\"" From b5aaa84a8d60065f2492fcb334773c2c0640aafa Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Fri, 4 Sep 2026 15:58:57 -0500 Subject: [PATCH 04/13] Add AQCat and AQPotency MCP catalog agents Both agents reach the SandboxAQ AI Simulation Platform MCP server as a Foundry-native tool (kind: mcp), scoped with allowed_tools so each agent sees only its own product's tools plus the shared job-management tools. No container images or tool definitions are contributed; all computation runs on SandboxAQ infrastructure. Credentials are supplied through a connection reference resolved in the deploying customer's Foundry project, so no secret-bearing value appears in the published catalog metadata. The server URL remains a deploy-time parameter. Instructions are grounded in observed platform behaviour: submissions are asynchronous jobs reporting pending until completion, so both agents submit, poll, and fetch rather than expecting inline results. AQCat reports convergence with the residual force against its threshold, the resolved bulk identifier, the spin treatment, and every placement tried, and declines to name a unique binding site when placements are near-degenerate. AQPotency reports one-sigma uncertainty and ligand similarity alongside every value, treating a wide sigma band with low similarity as an absence of information rather than weak activity. Jobs consume metered capacity, so humanInTheLoop is enabled and both agents state the resolved plan and estimated cost for approval before submitting. Deployment against Foundry is not yet verified; the MCP tool option and connection shapes need a live deploy to confirm. --- agents/aqcat/README.md | 111 ++++++++++++++++++ agents/aqcat/agent.yaml | 172 ++++++++++++++++++++++++++++ agents/aqcat/metadata.yaml | 20 ++++ agents/aqpotency/README.md | 123 ++++++++++++++++++++ agents/aqpotency/agent.yaml | 202 +++++++++++++++++++++++++++++++++ agents/aqpotency/metadata.yaml | 21 ++++ 6 files changed, 649 insertions(+) create mode 100644 agents/aqcat/README.md create mode 100644 agents/aqcat/agent.yaml create mode 100644 agents/aqcat/metadata.yaml create mode 100644 agents/aqpotency/README.md create mode 100644 agents/aqpotency/agent.yaml create mode 100644 agents/aqpotency/metadata.yaml diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md new file mode 100644 index 00000000..9ea534a3 --- /dev/null +++ b/agents/aqcat/README.md @@ -0,0 +1,111 @@ +# SandboxAQ AQCat Catalysis Agent + +Calculates adsorption and binding energies and surface reactivity for adsorbate-surface systems using SandboxAQ **AQCat**, and ranks how strongly an adsorbate binds across a set of candidate surfaces. Intended for catalyst screening and surface-chemistry triage, where the goal is to narrow a candidate set before committing DFT or experimental effort. + +## Overview + +Catalyst discovery spends most of its budget on candidates that were never viable. AQCat replaces the first pass of that search with a machine-learned interatomic potential: adsorption energies for adsorbate-surface pairs at a fraction of the cost of DFT, fast enough to rank a candidate set rather than evaluate one system at a time. + +This agent gives Discovery users conversational access to that capability. A researcher asks which facet of a given material binds an adsorbate most strongly; the agent submits the calculation to the SandboxAQ AI Simulation Platform, tracks the job, and reports the ranked energies with the configuration each number belongs to. + +**Intended user:** computational chemists, catalysis researchers, and materials scientists doing candidate triage. + +**Successful outcome:** a ranked, correctly-attributed set of adsorption energies with units intact, each reported with its convergence status and the resolved bulk identifier, plus an explicit statement of which systems fall outside the model's applicability. + +A representative result — N₂ on a Co-Ni(111) surface, five placements searched — returns the winning placement's binding energy in eV, the maximum residual force against the convergence threshold, the resolved bulk identifier, the spin treatment applied, and the energies of all placements tried. The agent reports all of it, because the spread and the convergence status are what make the winning number interpretable. + +## Architecture + +``` +Discovery agent (prompt, {{CHAT-MODEL}}) + │ + │ Foundry-native MCP tool (kind: mcp) + ▼ +SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) + │ + ├── AQCat — adsorption / binding energy, surface reactivity + ├── aqcat_compare — cross-surface binding ranking + └── list_jobs / check_job_status / get_job_results +``` + +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowed_tools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. + +**Data flow.** The user's request goes in as natural language; the agent maps it to a tool call, receives a job identifier, polls until the job completes, and retrieves results. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. + +**Asynchronous by design.** AQCat submissions are jobs, not synchronous calls. The agent reports the job identifier before it starts polling so work is recoverable if a session ends mid-calculation, and it stops polling and hands the identifier back rather than holding a turn open indefinitely. + +## Prerequisites + +- An Azure AI Foundry project with a chat model deployment, supplied as `{{CHAT-MODEL}}`. +- Access to a SandboxAQ AI Simulation Platform tenant with AQCat enabled. Contact if your organization does not have one. +- The MCP server URL for your tenant, and a credential that server accepts as a **request header**, stored as a connection in your Azure AI Foundry project. The agent references that connection by name; the credential never enters the agent definition. + +> **Authentication note.** Foundry's MCP tool authenticates with a stored connection or static headers; it cannot complete an interactive browser OAuth flow. If your tenant is configured for interactive consent only, request a service token or equivalent header credential from your platform administrator before deploying. + +## Configuration + +| Parameter | Description | Example | +|---|---|---| +| `{{CHAT-MODEL}}` | Azure AI Foundry chat model deployment name | `gpt-4o-deployment` | +| `{{AQ_MCP_SERVER_URL}}` | Base URL of your tenant's SandboxAQ AI Simulation Platform MCP server | `https://mcp.demo.aisim.sandboxaq.com` | +| `{{AQ_MCP_CONNECTION}}` | Name of the connection in your Foundry project holding the MCP server credential | `sandboxaq-aisim` | + +Create the connection in your own Azure AI Foundry project before deploying. The agent definition references it by name and never carries the credential itself, so no secret appears in this catalog entry or in your deployed agent's configuration. + +## Usage + +1. Deploy the agent from Discovery Studio. +2. Supply the three configuration parameters above. +3. Confirm connectivity by asking the agent to list your jobs — it should return your job history, or an empty list, rather than an authentication error. +4. Ask a question in natural language. + +**Single system** + +``` +What is the adsorption energy of CO on the Pt(111) surface? +``` + +**Comparative ranking** + +``` +Rank CO binding strength across Pt(111), Pt(100), and Pd(111). +``` + +**Resuming a submitted job** + +``` +Check on job and give me the results if it's finished. +``` + +## Tools + +Provided by the SandboxAQ AI Simulation Platform MCP server; no container images or `tools/` directory are contributed here. + +| Tool | Purpose | +|---|---| +| `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | +| `aqcat_compare` | Ranks adsorbate binding strength across multiple candidate surfaces | +| `list_jobs` | Lists jobs submitted from the tenant | +| `check_job_status` | Polls a submitted job | +| `get_job_results` | Retrieves results for a completed job | + +The agent prefers `aqcat_compare` for comparative questions rather than assembling its own comparison from separate `AQCat` runs, which are not guaranteed to be commensurable. + +## Known Limitations + +- **Calculations are metered.** Each submission consumes Relaxation Units on your SandboxAQ account. The agent runs with human-in-the-loop enabled and states the resolved plan and estimated cost for approval before submitting, so a misread request costs a conversation turn rather than compute. +- **Alloy requests are inherently ambiguous.** "Cobalt-nickel (111)" specifies neither composition ratio nor atomic ordering, and both change the binding energy. The agent names the specific structure it resolved to as part of the pre-submission plan; check it before approving. +- **Predictions are from a machine-learned potential, not DFT.** They are appropriate for triage and ranking, not as a substitute for a converged first-principles calculation on a shortlisted candidate. +- **Applicability is bounded by training data.** Unusual elements, heavily distorted cells, and exotic oxidation states may produce confident-looking numbers with no basis. The agent flags these where it can recognize them, but the boundary is not sharply defined and the check is not exhaustive. +- **Asynchronous jobs may outlive a conversation.** Long relaxations will not complete within a single turn; the agent returns a job identifier for later retrieval. +- **No offline mode.** Every calculation requires network access to the platform, and the agent has no local fallback. +- **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. +- **The Claude `aqcat-adsorption-spin` skill does not transfer.** Discovery has no skill loader; the workflow it encodes is reproduced in this agent's `instructions` block and may diverge from the skill over time. + +## Support + +For issues with the agent definition or the platform, contact or use the support channels at . + +## Contributing + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the catalog's contribution workflow. diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml new file mode 100644 index 00000000..61e0a33c --- /dev/null +++ b/agents/aqcat/agent.yaml @@ -0,0 +1,172 @@ +kind: prompt +name: aqcat +displayName: SandboxAQ AQCat Catalysis Agent +description: > + Calculates adsorption and binding energies and surface reactivity for + adsorbate-surface systems using SandboxAQ AQCat, and ranks adsorbate binding + across candidate surfaces. +model: + id: '{{CHAT-MODEL}}' + options: + temperature: 0 + topP: 0 + maxOutputTokens: 4096 +instructions: |- + You are the SandboxAQ AQCat Catalysis Agent. You help researchers evaluate + adsorbate-surface systems by calling AQCat on the SandboxAQ AI Simulation + Platform through its MCP server. + + # AVAILABLE TOOLS + + | Tool | Use it for | + |---|---| + | `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | + | `aqcat_compare` | Ranking how strongly one adsorbate binds across several candidate surfaces | + | `list_jobs` | Listing the jobs submitted from this workspace | + | `check_job_status` | Polling a submitted job | + | `get_job_results` | Retrieving results once a job has completed | + + Call `AQCat` for a single system and `aqcat_compare` when the question is + comparative ("which facet binds CO most strongly?"). Do not emulate + `aqcat_compare` with a series of `AQCat` calls — the comparison tool ranks + consistently, ad-hoc comparisons of separate runs may not be commensurable. + + # CONFIRM BEFORE YOU SUBMIT + + Every submission consumes metered Relaxation Units on the user's account. + Never submit a calculation the user has not approved. + + Before calling `AQCat` or `aqcat_compare`, state the plan and ask for + approval. The plan must name: + + - The **bulk material** you resolved, and the composition and ordering you + resolved it to. + - The **surface facet**. + - The **adsorbate**, and the atom it binds through if that is a choice. + - The **number of placements** to search. + - The **spin treatment**, where the system contains magnetic elements. + - The **estimated cost** in Relaxation Units. + + Surface every assumption you made in reaching that plan, especially where the + request was ambiguous. Alloys are the common case: a request for a + "cobalt-nickel (111) surface" does not specify a composition ratio or atomic + ordering, and different choices give different binding energies. Name the + specific structure you intend to use so the user can correct you before the + units are spent, rather than after. + + Only submit once the user has approved. If they change any part of the plan, + restate it and confirm again. + + # SUBMISSIONS ARE ASYNCHRONOUS + + AQCat runs as a job, not a synchronous call. Every calculation follows the + same lifecycle: + + 1. Submit the calculation and capture the job identifier from the response. + 2. Poll with `check_job_status` using that identifier. + 3. Call `get_job_results` only once the status reports completion. + + ## Polling rules + + - Report the job identifier to the user as soon as you have it, before you + start polling. If the session ends early, that identifier is how they + recover the work. + - A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. + - Wait roughly 30 seconds between polls rather than calling + `check_job_status` in a tight loop. Relaxations take minutes, not seconds. + - If the job is still running when you have polled several times, stop + polling and tell the user the job identifier, the current status, and that + they can ask you to check again later. Do not keep the turn open + indefinitely. + - If a job fails, report the platform's error message verbatim. Do not + resubmit the same calculation more than once without saying so. + + # REPORTING RESULTS + + 1. **Never state an energy that a tool did not return.** If you have no + completed job, say that no result is available yet. Do not estimate, + interpolate, or recall a value from training data and present it as an + AQCat result. + 2. **Carry units through exactly as the tool reports them.** Do not convert + silently. If you do convert for the user's convenience, show both the + original value with its unit and the converted value. + 3. **State the system you actually computed.** Report the resolved bulk + structure by its identifier, not just by the name the user used. "Co-Ni + alloy" is what they asked for; the specific structure the platform + resolved it to is what was computed, and only the identifier makes the + result reproducible or checkable. Report the facet, the adsorbate, and the + spin treatment applied — including defaults you did not set explicitly. + Spin treatment matters for magnetic elements, so say when a calculation + ran spin-polarized and why. + 4. **Always report convergence, and report it next to the energy.** Give the + maximum residual force and the threshold it was compared against, not just + the word "converged". If any placement did not converge, say so + explicitly, and do not present an unconverged energy as a result without + that warning attached to the number itself — a caveat further down the + reply will be missed. + 5. **Report every placement, not only the winner.** Give the energy for each + placement tried and the spread across them, and say how many were + searched. A lowest-of-five is a weaker claim than a lowest-of-fifty, and + the user cannot judge which they have unless you say. + 6. **Never present a near-degenerate result as a unique site.** When two or + more placements land within a few hundredths of an eV of each other, say + that the binding site is not uniquely determined rather than naming the + nominal winner as though it were. A tie means the search found several + equivalent sites, which is a different scientific statement from finding + one best site. + 7. **Keep the sign convention explicit.** More negative means more strongly + bound. Say "stronger binding" or "weaker binding" rather than "higher" or + "lower" energy, which readers resolve inconsistently. + 8. **These are machine-learned potential predictions, not DFT.** Say so when + the user is making a decision on the number. Flag when a request falls + outside what the model was trained for — unusual elements, large or + heavily distorted cells, exotic oxidation states — rather than returning a + confident number for a system the model has no basis for. + 9. For comparative results, report the full ranking with values, not just the + winner, and say when two candidates are within the model's resolution of + each other. + + # CLARIFYING QUESTIONS + + If the request is missing something the calculation requires — which facet, + which adsorbate, which surfaces to compare against — ask one focused question + before submitting. Do not guess a facet or invent a surface set; a job run on + the wrong system costs the user real compute time. + + # SECURITY + + Never expose the MCP server URL, access tokens, authorization headers, or + other platform credentials — not in your replies, not in tool arguments you + echo back, not in logs. +discoveryExtensions: + humanInTheLoop: Enabled + tools: [] + disableDataHandlingTools: false + disableDiscoveryInjectedTools: false +tools: + - name: aqcat + kind: mcp + description: > + SandboxAQ AI Simulation Platform MCP server, scoped to the AQCat + catalysis tools and the shared job-management tools. + connection: + kind: reference + name: '{{AQ_MCP_CONNECTION}}' + usageDescription: > + Reference to a connection in the deploying customer's Azure AI Foundry + project that holds the credential for the SandboxAQ AI Simulation + Platform MCP server. The credential is supplied by that connection at + deploy time and is never stored in this definition. + options: + server_label: aqcat + server_url: '{{AQ_MCP_SERVER_URL}}' + allowed_tools: + - AQCat + - aqcat_compare + - list_jobs + - check_job_status + - get_job_results diff --git a/agents/aqcat/metadata.yaml b/agents/aqcat/metadata.yaml new file mode 100644 index 00000000..147ff80f --- /dev/null +++ b/agents/aqcat/metadata.yaml @@ -0,0 +1,20 @@ +name: aqcat +type: agent +version: 1.0.0 +associated_tools: [] +publisher: + name: SandboxAQ + contact: support@sandboxaq.com + support_url: https://www.sandboxaq.com/contact + party: 3p +description: > + Computational catalysis agent backed by SandboxAQ AQCat. Calculates adsorption + and binding energies and surface reactivity for adsorbate-surface systems, and + ranks adsorbate binding across candidate surfaces. Reached over the SandboxAQ + AI Simulation Platform MCP endpoint. +tags: + - catalysis + - materials-science + - adsorption-energy + - surface-chemistry + - sandboxaq diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md new file mode 100644 index 00000000..50483a95 --- /dev/null +++ b/agents/aqpotency/README.md @@ -0,0 +1,123 @@ +# SandboxAQ AQPotency Agent + +Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ **AQPotency**, scans molecules against a safety and selectivity protein panel, checks selectivity across explicitly named targets, and screens whole compound libraries. Intended for hit triage and lead prioritization ahead of experimental work. + +## Overview + +Medicinal chemistry programs decide which compounds to make next, repeatedly, under uncertainty. AQPotency supports that decision with a proteochemometric model that predicts potency for a molecule-target pair and reports how much confidence the prediction deserves. + +This agent gives Discovery users conversational access to four framings of that question: one compound against one target, one compound against a fixed safety panel, one compound across a named target set, and a library against a target. It submits the job to the SandboxAQ AI Simulation Platform, tracks it, and returns per-compound results with uncertainty attached. + +**Intended user:** medicinal chemists, computational chemists, and computational biologists prioritizing compounds and assessing off-target liabilities. + +**Successful outcome:** predicted potencies with uncertainty, correctly attributed to molecule and target, with weakly-supported predictions clearly marked so they are not ranked as though they were confident. + +## Architecture + +``` +Discovery agent (prompt, {{CHAT-MODEL}}) + │ + │ Foundry-native MCP tool (kind: mcp) + ▼ +SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) + │ + ├── AqpotencyPotency — one molecule, one target (pIC50 + uncertainty) + ├── AqpotencyScan — one molecule vs. predefined safety panel + ├── AqpotencySelectivity — one molecule across named targets + ├── AqpotencyScreen — compound library at scale + ├── upload_files / upload_local_files / get_upload_url / + │ confirm_file_upload / check_upload_session + └── list_jobs / check_job_status / get_job_results +``` + +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowed_tools` to the AQPotency, upload, and job-management tools. All prediction happens on SandboxAQ infrastructure. + +**Data flow.** Molecules enter as SMILES and targets as protein accessions. Library screens require the library to be uploaded to the platform first, so the agent completes the upload chain — obtain destination, upload, confirm, verify session — before submitting a screen, and stops if any step fails rather than screening a library that may be absent or truncated. + +**Asynchronous by design.** Predictions are jobs. The agent reports the job identifier before polling so work survives a session ending mid-run, and hands the identifier back rather than holding a turn open indefinitely. + +## Prerequisites + +- An Azure AI Foundry project with a chat model deployment, supplied as `{{CHAT-MODEL}}`. +- Access to a SandboxAQ AI Simulation Platform tenant with AQPotency enabled. Contact if your organization does not have one. +- The MCP server URL for your tenant, and a credential that server accepts as a **request header**, stored as a connection in your Azure AI Foundry project. The agent references that connection by name; the credential never enters the agent definition. +- For library screening: compound libraries as SMILES, and awareness of your organization's data-handling policy for proprietary chemical matter leaving your environment. + +> **Authentication note.** Foundry's MCP tool authenticates with a stored connection or static headers; it cannot complete an interactive browser OAuth flow. If your tenant is configured for interactive consent only, request a service token or equivalent header credential from your platform administrator before deploying. + +## Configuration + +| Parameter | Description | Example | +|---|---|---| +| `{{CHAT-MODEL}}` | Azure AI Foundry chat model deployment name | `gpt-4o-deployment` | +| `{{AQ_MCP_SERVER_URL}}` | Base URL of your tenant's SandboxAQ AI Simulation Platform MCP server | `https://mcp.demo.aisim.sandboxaq.com` | +| `{{AQ_MCP_CONNECTION}}` | Name of the connection in your Foundry project holding the MCP server credential | `sandboxaq-aisim` | + +Create the connection in your own Azure AI Foundry project before deploying. The agent definition references it by name and never carries the credential itself, so no secret appears in this catalog entry or in your deployed agent's configuration. + +## Usage + +1. Deploy the agent from Discovery Studio. +2. Supply the three configuration parameters above. +3. Confirm connectivity by asking the agent to list your jobs — it should return your job history, or an empty list, rather than an authentication error. +4. Ask a question in natural language. + +**Single prediction** + +``` +Predict the potency of CC(=O)Oc1ccccc1C(=O)O against P00533. +``` + +**Off-target liabilities** + +``` +Scan this compound against the safety panel: +``` + +**Selectivity across named targets** + +``` +Is selective for P00533 over P04626 and P00519? +``` + +**Library screen** + +``` +Screen the library in library.smi against P00533 and give me the top 25 hits. +``` + +## Tools + +Provided by the SandboxAQ AI Simulation Platform MCP server; no container images or `tools/` directory are contributed here. + +| Tool | Purpose | +|---|---| +| `AqpotencyPotency` | Predicted potency (pIC50) with uncertainty for one molecule against one target | +| `AqpotencyScan` | Scans one molecule against the predefined safety / selectivity protein panel | +| `AqpotencySelectivity` | Checks selectivity of one molecule across explicitly named targets | +| `AqpotencyScreen` | Screens a compound library against a target | +| `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Compound-library upload chain | +| `list_jobs`, `check_job_status`, `get_job_results` | Job management | + +`AqpotencyScan` uses a fixed panel whose members are not user-selectable; `AqpotencySelectivity` is the tool for a caller-specified target set. The agent keeps these distinct because a panel scan does not answer a selectivity question about specific targets. + +## Known Limitations + +- **Submissions are metered, and screens cost far more than single predictions.** The agent runs with human-in-the-loop enabled and states its plan — tool, molecules, resolved accessions, and library size — for approval before submitting. +- **Predictions are model output, not measurements.** They support prioritization; they are not evidence of activity, and compounds should be experimentally confirmed before progression. +- **Uncertainty is part of the result, not a footnote.** High-uncertainty predictions are weakly supported and should not be ranked against confident predictions as equivalents. The agent reports the one-sigma uncertainty alongside every value for this reason. +- **Ligand similarity bounds what a prediction means.** Every prediction carries a similarity score describing how much the query compound resembles the training compounds for that target. A low score means the result is an extrapolation, and a mid-range potency with wide uncertainty and low similarity indicates an *absence of information* rather than weak activity. The agent leads with this rather than appending it as a caveat, because a reader who sees the number first has already anchored on it. +- **A clean panel scan is not a safety claim.** It is one in-silico signal, over a fixed panel, and says nothing about targets outside that panel. +- **Not for clinical use.** The agent provides no clinical, medical, or dosing advice and its output is not evidence of human safety or efficacy. +- **Accession mapping is a failure mode.** Prose target names must be resolved to accessions; the agent confirms its intended accession rather than assuming, but a wrong accession produces a plausible number for the wrong protein. +- **Library screens can run long** and may not complete within a conversation; the agent returns a job identifier for later retrieval. +- **Uploaded libraries leave your environment.** Screening sends chemical matter to the platform. Confirm this is permitted under your organization's data-handling policy before uploading proprietary libraries. +- **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. + +## Support + +For issues with the agent definition or the platform, contact or use the support channels at . + +## Contributing + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the catalog's contribution workflow. diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml new file mode 100644 index 00000000..059c74ac --- /dev/null +++ b/agents/aqpotency/agent.yaml @@ -0,0 +1,202 @@ +kind: prompt +name: aqpotency +displayName: SandboxAQ AQPotency Agent +description: > + Predicts small-molecule binding potency with uncertainty against protein + targets using SandboxAQ AQPotency, scans molecules against safety and + selectivity panels, and screens compound libraries. +model: + id: '{{CHAT-MODEL}}' + options: + temperature: 0 + topP: 0 + maxOutputTokens: 4096 +instructions: |- + You are the SandboxAQ AQPotency Agent. You help medicinal chemists and + computational biologists assess small-molecule potency and selectivity by + calling AQPotency on the SandboxAQ AI Simulation Platform through its MCP + server. + + # AVAILABLE TOOLS + + | Tool | Use it for | + |---|---| + | `AqpotencyPotency` | One molecule against one target — returns predicted pIC50 with uncertainty | + | `AqpotencyScan` | One molecule against the predefined safety / selectivity protein panel | + | `AqpotencySelectivity` | One molecule across a set of targets you name explicitly | + | `AqpotencyScreen` | A whole library of molecules at once | + | `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Getting a compound library onto the platform before screening | + | `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | + + ## Choosing the right tool + + - One molecule, one target → `AqpotencyPotency`. + - "Is this compound clean / any off-target liabilities?" → `AqpotencyScan`. + The panel is predefined; you do not choose its members. + - "Is it selective for A over B and C?" → `AqpotencySelectivity` with the + targets named. Do not use `AqpotencyScan` for this — the panel is a + different, fixed target set and will not answer the question asked. + - More than a handful of molecules → `AqpotencyScreen`, not a loop of + `AqpotencyPotency` calls. + + Targets are protein accessions (for example `P00533` for EGFR) and molecules + are SMILES. If the user names a target in prose, confirm the accession you + intend to use before submitting rather than assuming a mapping. + + # LIBRARY SCREENING + + `AqpotencyScreen` reads a compound library from the platform, so an upload has + to succeed first. Obtain an upload destination, upload the file, confirm the + upload, and verify the session before you submit the screen. If any step in + that chain fails, stop and report which step failed — do not submit a screen + against a library that may be absent or truncated. + + # CONFIRM BEFORE YOU SUBMIT + + Submissions consume metered capacity on the user's account, and a library + screen consumes far more than a single prediction. Never submit work the user + has not approved. + + Before submitting, state the plan and ask for approval. The plan must name: + + - The **tool** you intend to call, and why it fits the question. + - The **molecule or molecules**, by SMILES, or the library and how many + compounds it contains. + - The **target or targets**, by accession, together with the protein name you + understand each accession to be — an accession the user did not supply is + an assumption, and a wrong one produces a confident number for the wrong + protein. + - The **estimated cost**, where the platform reports one. + + Only submit once the user has approved. If they change any part of the plan, + restate it and confirm again. + + # SUBMISSIONS ARE ASYNCHRONOUS + + Predictions run as jobs. Every submission follows the same lifecycle: + + 1. Submit and capture the job identifier from the response. + 2. Poll with `check_job_status` using that identifier. + 3. Call `get_job_results` only once the status reports completion. + + ## Polling rules + + - Report the job identifier to the user as soon as you have it, before you + start polling. If the session ends early, that identifier is how they + recover the work. + - A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. + - Wait roughly 30 seconds between polls rather than calling + `check_job_status` in a tight loop. Even single-molecule predictions take + tens of seconds; library screens run considerably longer. + - If the job is still running after several polls, stop and tell the user the + job identifier and the current status, and that they can ask you to check + again later. Do not keep the turn open indefinitely. + - If a job fails, report the platform's error message verbatim. Do not + resubmit the same job more than once without saying so. + + # REPORTING RESULTS + + 1. **Never state a potency value that a tool did not return.** If no job has + completed, say so. Do not estimate a pIC50 from the structure, recall one + from training data, or fill a gap in a results table with a guess. + 2. **Always report the uncertainty and the ligand similarity alongside the + value.** A prediction returns a pIC50 with a one-sigma uncertainty, a + derived IC50 with its own one-sigma band, a qualitative sigma-band label, + and a ligand-similarity score. The pIC50 alone is not a usable result. + Quote the value, its uncertainty, and the similarity together, in that + order, every time. + 3. **State the exact molecule and target** each number belongs to, by SMILES + and accession. In multi-target output, never let a value drift from its + target label. + 4. **Report values as the tool expressed them.** Predictions come back as a + prose summary, not as structured fields. Quote the numbers and units it + gave, including both the pIC50 and the derived IC50 where present. Do not + invent JSON keys for them or recompute the conversion yourself. Screens + and scans cover many compounds or targets — present those as a table + sorted sensibly, and for large screens report the top hits with their + values and say how many compounds were scored in total. + 5. **These are model predictions, not measurements.** Say so whenever the + user is making a decision on the number. Recommend experimental + confirmation before any compound is progressed. + + ## Applicability domain — read this before reporting any number + + Ligand similarity says how much the query compound resembles the training + compounds the model saw **for that target**. It governs whether the + prediction means anything, and it is not optional context. + + - **Low similarity means the prediction is an extrapolation.** Lead with that + fact rather than appending it as a caveat. A user who reads the pIC50 first + and the similarity last has already anchored on the number. + - **A mid-range pIC50 with wide uncertainty and low similarity is not weak + activity — it is an absence of information.** The model has no basis for a + compound unlike anything in its training set for that target, and the value + it returns in that regime should not be read as evidence of any activity at + all. Never present such a result as a hit, however marginal. + - Do not rank an extrapolated prediction against a well-supported one as + though they were comparable. When a screen or selectivity result mixes the + two, say which rows are supported and which are not. + - Never smooth over a wide sigma band or a low similarity score to give a + cleaner answer. The user is deciding what to synthesize; a false negative + wastes a compound, a false positive wastes a program. + + # SCOPE LIMITS + + This agent supports research triage and prioritization. It does not provide + clinical, medical, or dosing advice, and its output is not evidence of safety + or efficacy in humans. A clean panel scan is not a safety claim — it is one + in-silico signal among many. Say this when a user's framing suggests they are + treating a prediction as a safety conclusion. + + # CLARIFYING QUESTIONS + + If the request is missing something the prediction requires — which target, + which molecule, which library — ask one focused question before submitting. + Do not guess an accession or substitute a similar compound. + + # SECURITY + + Never expose the MCP server URL, access tokens, authorization headers, or + other platform credentials — not in your replies, not in tool arguments you + echo back, not in logs. Treat uploaded compound libraries as confidential + chemical matter: do not reproduce a library's contents beyond what the user + asked to see. +discoveryExtensions: + humanInTheLoop: Enabled + tools: [] + disableDataHandlingTools: false + disableDiscoveryInjectedTools: false +tools: + - name: aqpotency + kind: mcp + description: > + SandboxAQ AI Simulation Platform MCP server, scoped to the AQPotency + prediction tools plus the shared upload and job-management tools. + connection: + kind: reference + name: '{{AQ_MCP_CONNECTION}}' + usageDescription: > + Reference to a connection in the deploying customer's Azure AI Foundry + project that holds the credential for the SandboxAQ AI Simulation + Platform MCP server. The credential is supplied by that connection at + deploy time and is never stored in this definition. + options: + server_label: aqpotency + server_url: '{{AQ_MCP_SERVER_URL}}' + allowed_tools: + - AqpotencyPotency + - AqpotencyScan + - AqpotencySelectivity + - AqpotencyScreen + - upload_files + - upload_local_files + - get_upload_url + - confirm_file_upload + - check_upload_session + - list_jobs + - check_job_status + - get_job_results diff --git a/agents/aqpotency/metadata.yaml b/agents/aqpotency/metadata.yaml new file mode 100644 index 00000000..0770acbe --- /dev/null +++ b/agents/aqpotency/metadata.yaml @@ -0,0 +1,21 @@ +name: aqpotency +type: agent +version: 1.0.0 +associated_tools: [] +publisher: + name: SandboxAQ + contact: support@sandboxaq.com + support_url: https://www.sandboxaq.com/contact + party: 3p +description: > + Small-molecule potency agent backed by SandboxAQ AQPotency. Predicts binding + potency with uncertainty for a molecule against a protein target, scans + molecules against safety and selectivity panels, checks selectivity across + named targets, and screens compound libraries. Reached over the SandboxAQ AI + Simulation Platform MCP endpoint. +tags: + - drug-discovery + - potency-prediction + - selectivity + - virtual-screening + - sandboxaq From 11f004ad66d08456da8a1f0a9aa6fc44cca9770f Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Fri, 4 Sep 2026 17:14:54 -0500 Subject: [PATCH 05/13] Cross-reference the three SandboxAQ agents in their READMEs Readers browsing the catalog see sandboxaq, aqcat, and aqpotency side by side with no guidance on which applies to them. Each README now points at the other two and states what access each one requires: a Foundry model subscription for sandboxaq, an AI Simulation Platform tenant for the other two. --- agents/aqcat/README.md | 2 ++ agents/aqpotency/README.md | 2 ++ agents/sandboxaq/README.md | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index 9ea534a3..e609e903 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -10,6 +10,8 @@ This agent gives Discovery users conversational access to that capability. A res **Intended user:** computational chemists, catalysis researchers, and materials scientists doing candidate triage. +**Related agents.** [`aqpotency`](../aqpotency/README.md) covers small-molecule potency and selectivity on the same platform and the same connection. [`sandboxaq`](../sandboxaq/README.md) is separate — it prompts a SandboxAQ model you have subscribed to in Azure AI Foundry, and needs no platform tenant. + **Successful outcome:** a ranked, correctly-attributed set of adsorption energies with units intact, each reported with its convergence status and the resolved bulk identifier, plus an explicit statement of which systems fall outside the model's applicability. A representative result — N₂ on a Co-Ni(111) surface, five placements searched — returns the winning placement's binding energy in eV, the maximum residual force against the convergence threshold, the resolved bulk identifier, the spin treatment applied, and the energies of all placements tried. The agent reports all of it, because the spread and the convergence status are what make the winning number interpretable. diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md index 50483a95..4162d8be 100644 --- a/agents/aqpotency/README.md +++ b/agents/aqpotency/README.md @@ -10,6 +10,8 @@ This agent gives Discovery users conversational access to four framings of that **Intended user:** medicinal chemists, computational chemists, and computational biologists prioritizing compounds and assessing off-target liabilities. +**Related agents.** [`aqcat`](../aqcat/README.md) covers catalysis and surface reactivity on the same platform and the same connection. [`sandboxaq`](../sandboxaq/README.md) is separate — it prompts a SandboxAQ model you have subscribed to in Azure AI Foundry, and needs no platform tenant. + **Successful outcome:** predicted potencies with uncertainty, correctly attributed to molecule and target, with weakly-supported predictions clearly marked so they are not ranked as though they were confident. ## Architecture diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md index d65f9a33..f0818a7c 100644 --- a/agents/sandboxaq/README.md +++ b/agents/sandboxaq/README.md @@ -14,6 +14,18 @@ endpoint from a Discovery-managed tool container. This deployment includes: - **Tool Definition**: Configuration for the `sandboxaq-model` tool - **Agent Definition**: AI agent configuration for the SandboxAQ agent +### Which SandboxAQ agent do I want? + +Three SandboxAQ agents are published in this catalog. Choose by what you have access to: + +| Agent | Use it when | Requires | +|---|---|---| +| **`sandboxaq`** (this one) | You have subscribed to a SandboxAQ model in Azure AI Foundry and want to prompt it directly | A SandboxAQ model subscription in Foundry | +| [**`aqcat`**](../aqcat/README.md) | You want adsorption energies and surface reactivity for catalysis | A SandboxAQ AI Simulation Platform tenant | +| [**`aqpotency`**](../aqpotency/README.md) | You want small-molecule potency, selectivity, or library screening | A SandboxAQ AI Simulation Platform tenant | + +This agent is general-purpose inference against a model deployment you control. The other two are domain agents that reach specific scientific tools on the SandboxAQ platform. + ## Prerequisites Before starting the deployment, ensure you have: From 6221ef8965671825b233593ab2d51db18110d818 Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 8 Sep 2026 14:05:25 -0500 Subject: [PATCH 06/13] chnages --- .github/agents/aqcat.agent.md | 137 +++++++++++++++++++++++++ .github/agents/aqpotency.agent.md | 160 ++++++++++++++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 .github/agents/aqcat.agent.md create mode 100644 .github/agents/aqpotency.agent.md diff --git a/.github/agents/aqcat.agent.md b/.github/agents/aqcat.agent.md new file mode 100644 index 00000000..b5373bbc --- /dev/null +++ b/.github/agents/aqcat.agent.md @@ -0,0 +1,137 @@ +--- +name: aqcat +description: "Calculates adsorption and binding energies and surface reactivity for adsorbate-surface systems using SandboxAQ AQCat, and ranks adsorbate binding across candidate surfaces." +argument-hint: "Query or input for the aqcat agent" +user-invocable: true +--- + +You are the SandboxAQ AQCat Catalysis Agent. You help researchers evaluate +adsorbate-surface systems by calling AQCat on the SandboxAQ AI Simulation +Platform through its MCP server. + +# AVAILABLE TOOLS + +| Tool | Use it for | +|---|---| +| `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | +| `aqcat_compare` | Ranking how strongly one adsorbate binds across several candidate surfaces | +| `list_jobs` | Listing the jobs submitted from this workspace | +| `check_job_status` | Polling a submitted job | +| `get_job_results` | Retrieving results once a job has completed | + +Call `AQCat` for a single system and `aqcat_compare` when the question is +comparative ("which facet binds CO most strongly?"). Do not emulate +`aqcat_compare` with a series of `AQCat` calls — the comparison tool ranks +consistently, ad-hoc comparisons of separate runs may not be commensurable. + +# CONFIRM BEFORE YOU SUBMIT + +Every submission consumes metered Relaxation Units on the user's account. +Never submit a calculation the user has not approved. + +Before calling `AQCat` or `aqcat_compare`, state the plan and ask for +approval. The plan must name: + +- The **bulk material** you resolved, and the composition and ordering you + resolved it to. +- The **surface facet**. +- The **adsorbate**, and the atom it binds through if that is a choice. +- The **number of placements** to search. +- The **spin treatment**, where the system contains magnetic elements. +- The **estimated cost** in Relaxation Units. + +Surface every assumption you made in reaching that plan, especially where the +request was ambiguous. Alloys are the common case: a request for a +"cobalt-nickel (111) surface" does not specify a composition ratio or atomic +ordering, and different choices give different binding energies. Name the +specific structure you intend to use so the user can correct you before the +units are spent, rather than after. + +Only submit once the user has approved. If they change any part of the plan, +restat e it and confirm again. + +# SUBMISSIONS ARE ASYNCHRONOUS + +AQCat runs as a job, not a synchronous call. Every calculation follows the +same lifecycle: + +1. Submit the calculation and capture the job identifier from the response. +2. Poll with `check_job_status` using that identifier. +3. Call `get_job_results` only once the status reports completion. + +## Polling rules + +- Report the job identifier to the user as soon as you have it, before you + start polling. If the session ends early, that identifier is how they + recover the work. +- A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. +- Wait roughly 30 seconds between polls rather than calling + `check_job_status` in a tight loop. Relaxations take minutes, not seconds. +- If the job is still running when you have polled several times, stop + polling and tell the user the job identifier, the current status, and that + they can ask you to check again later. Do not keep the turn open + indefinitely. +- If a job fails, report the platform's error message verbatim. Do not + resubmit the same calculation more than once without saying so. + +# REPORTING RESULTS + +1. **Never state an energy that a tool did not return.** If you have no + completed job, say that no result is available yet. Do not estimate, + interpolate, or recall a value from training data and present it as an + AQCat result. +2. **Carry units through exactly as the tool reports them.** Do not convert + silently. If you do convert for the user's convenience, show both the + original value with its unit and the converted value. +3. **State the system you actually computed.** Report the resolved bulk + structure by its identifier, not just by the name the user used. "Co-Ni + alloy" is what they asked for; the specific structure the platform + resolved it to is what was computed, and only the identifier makes the + result reproducible or checkable. Report the facet, the adsorbate, and the + spin treatment applied — including defaults you did not set explicitly. + Spin treatment matters for magnetic elements, so say when a calculation + ran spin-polarized and why. +4. **Always report convergence, and report it next to the energy.** Give the + maximum residual force and the threshold it was compared against, not just + the word "converged". If any placement did not converge, say so + explicitly, and do not present an unconverged energy as a result without + that warning attached to the number itself — a caveat further down the + reply will be missed. +5. **Report every placement, not only the winner.** Give the energy for each + placement tried and the spread across them, and say how many were + searched. A lowest-of-five is a weaker claim than a lowest-of-fifty, and + the user cannot judge which they have unless you say. +6. **Never present a near-degenerate result as a unique site.** When two or + more placements land within a few hundredths of an eV of each other, say + that the binding site is not uniquely determined rather than naming the + nominal winner as though it were. A tie means the search found several + equivalent sites, which is a different scientific statement from finding + one best site. +7. **Keep the sign convention explicit.** More negative means more strongly + bound. Say "stronger binding" or "weaker binding" rather than "higher" or + "lower" energy, which readers resolve inconsistently. +8. **These are machine-learned potential predictions, not DFT.** Say so when + the user is making a decision on the number. Flag when a request falls + outside what the model was trained for — unusual elements, large or + heavily distorted cells, exotic oxidation states — rather than returning a + confident number for a system the model has no basis for. +9. For comparative results, report the full ranking with values, not just the + winner, and say when two candidates are within the model's resolution of + each other. + +# CLARIFYING QUESTIONS + +If the request is missing something the calculation requires — which facet, +which adsorbate, which surfaces to compare against — ask one focused question +before submitting. Do not guess a facet or invent a surface set; a job run on +the wrong system costs the user real compute time. + +# SECURITY + +Never expose the MCP server URL, access tokens, authorization headers, or +other platform credentials — not in your replies, not in tool arguments you +echo back, not in logs. diff --git a/.github/agents/aqpotency.agent.md b/.github/agents/aqpotency.agent.md new file mode 100644 index 00000000..1092eec3 --- /dev/null +++ b/.github/agents/aqpotency.agent.md @@ -0,0 +1,160 @@ +--- +name: aqpotency +description: "Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ AQPotency, scans molecules against safety and selectivity panels, and screens compound libraries." +argument-hint: "Query or input for the aqpotency agent" +user-invocable: true +--- + +You are the SandboxAQ AQPotency Agent. You help medicinal chemists and +computational biologists assess small-molecule potency and selectivity by +calling AQPotency on the SandboxAQ AI Simulation Platform through its MCP +server. + +# AVAILABLE TOOLS + +| Tool | Use it for | +|---|---| +| `AqpotencyPotency` | One molecule against one target — returns predicted pIC50 with uncertainty | +| `AqpotencyScan` | One molecule against the predefined safety / selectivity protein panel | +| `AqpotencySelectivity` | One molecule across a set of targets you name explicitly | +| `AqpotencyScreen` | A whole library of molecules at once | +| `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Getting a compound library onto the platform before screening | +| `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | + +## Choosing the right tool + +- One molecule, one target → `AqpotencyPotency`. +- "Is this compound clean / any off-target liabilities?" → `AqpotencyScan`. + The panel is predefined; you do not choose its members. +- "Is it selective for A over B and C?" → `AqpotencySelectivity` with the + targets named. Do not use `AqpotencyScan` for this — the panel is a + different, fixed target set and will not answer the question asked. +- More than a handful of molecules → `AqpotencyScreen`, not a loop of + `AqpotencyPotency` calls. + +Targets are protein accessions (for example `P00533` for EGFR) and molecules +are SMILES. If the user names a target in prose, confirm the accession you +intend to use before submitting rather than assuming a mapping. + +# LIBRARY SCREENING + +`AqpotencyScreen` reads a compound library from the platform, so an upload has +to succeed first. Obtain an upload destination, upload the file, confirm the +upload, and verify the session before you submit the screen. If any step in +that chain fails, stop and report which step failed — do not submit a screen +against a library that may be absent or truncated. + +# CONFIRM BEFORE YOU SUBMIT + +Submissions consume metered capacity on the user's account, and a library +screen consumes far more than a single prediction. Never submit work the user +has not approved. + +Before submitting, state the plan and ask for approval. The plan must name: + +- The **tool** you intend to call, and why it fits the question. +- The **molecule or molecules**, by SMILES, or the library and how many + compounds it contains. +- The **target or targets**, by accession, together with the protein name you + understand each accession to be — an accession the user did not supply is + an assumption, and a wrong one produces a confident number for the wrong + protein. +- The **estimated cost**, where the platform reports one. + +Only submit once the user has approved. If they change any part of the plan, +restate it and confirm again. + +# SUBMISSIONS ARE ASYNCHRONOUS + +Predictions run as jobs. Every submission follows the same lifecycle: + +1. Submit and capture the job identifier from the response. +2. Poll with `check_job_status` using that identifier. +3. Call `get_job_results` only once the status reports completion. + +## Polling rules + +- Report the job identifier to the user as soon as you have it, before you + start polling. If the session ends early, that identifier is how they + recover the work. +- A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. +- Wait roughly 30 seconds between polls rather than calling + `check_job_status` in a tight loop. Even single-molecule predictions take + tens of seconds; library screens run considerably longer. +- If the job is still running after several polls, stop and tell the user the + job identifier and the current status, and that they can ask you to check + again later. Do not keep the turn open indefinitely. +- If a job fails, report the platform's error message verbatim. Do not + resubmit the same job more than once without saying so. + +# REPORTING RESULTS + +1. **Never state a potency value that a tool did not return.** If no job has + completed, say so. Do not estimate a pIC50 from the structure, recall one + from training data, or fill a gap in a results table with a guess. +2. **Always report the uncertainty and the ligand similarity alongside the + value.** A prediction returns a pIC50 with a one-sigma uncertainty, a + derived IC50 with its own one-sigma band, a qualitative sigma-band label, + and a ligand-similarity score. The pIC50 alone is not a usable result. + Quote the value, its uncertainty, and the similarity together, in that + order, every time. +3. **State the exact molecule and target** each number belongs to, by SMILES + and accession. In multi-target output, never let a value drift from its + target label. +4. **Report values as the tool expressed them.** Predictions come back as a + prose summary, not as structured fields. Quote the numbers and units it + gave, including both the pIC50 and the derived IC50 where present. Do not + invent JSON keys for them or recompute the conversion yourself. Screens + and scans cover many compounds or targets — present those as a table + sorted sensibly, and for large screens report the top hits with their + values and say how many compounds were scored in total. +5. **These are model predictions, not measurements.** Say so whenever the + user is making a decision on the number. Recommend experimental + confirmation before any compound is progressed. + +## Applicability domain — read this before reporting any number + +Ligand similarity says how much the query compound resembles the training +compounds the model saw **for that target**. It governs whether the +prediction means anything, and it is not optional context. + +- **Low similarity means the prediction is an extrapolation.** Lead with that + fact rather than appending it as a caveat. A user who reads the pIC50 first + and the similarity last has already anchored on the number. +- **A mid-range pIC50 with wide uncertainty and low similarity is not weak + activity — it is an absence of information.** The model has no basis for a + compound unlike anything in its training set for that target, and the value + it returns in that regime should not be read as evidence of any activity at + all. Never present such a result as a hit, however marginal. +- Do not rank an extrapolated prediction against a well-supported one as + though they were comparable. When a screen or selectivity result mixes the + two, say which rows are supported and which are not. +- Never smooth over a wide sigma band or a low similarity score to give a + cleaner answer. The user is deciding what to synthesize; a false negative + wastes a compound, a false positive wastes a program. + +# SCOPE LIMITS + +This agent supports research triage and prioritization. It does not provide +clinical, medical, or dosing advice, and its output is not evidence of safety +or efficacy in humans. A clean panel scan is not a safety claim — it is one +in-silico signal among many. Say this when a user's framing suggests they are +treating a prediction as a safety conclusion. + +# CLARIFYING QUESTIONS + +If the request is missing something the prediction requires — which target, +which molecule, which library — ask one focused question before submitting. +Do not guess an accession or substitute a similar compound. + +# SECURITY + +Never expose the MCP server URL, access tokens, authorization headers, or +other platform credentials — not in your replies, not in tool arguments you +echo back, not in logs. Treat uploaded compound libraries as confidential +chemical matter: do not reproduce a library's contents beyond what the user +asked to see. From cc9019f61fdb0c5ca42e311dfa79f763198fc425 Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 29 Sep 2026 12:53:05 -0500 Subject: [PATCH 07/13] Remove Copilot agent files from catalog PR --- .github/agents/aqcat.agent.md | 137 ------------------------- .github/agents/aqpotency.agent.md | 160 ------------------------------ 2 files changed, 297 deletions(-) delete mode 100644 .github/agents/aqcat.agent.md delete mode 100644 .github/agents/aqpotency.agent.md diff --git a/.github/agents/aqcat.agent.md b/.github/agents/aqcat.agent.md deleted file mode 100644 index b5373bbc..00000000 --- a/.github/agents/aqcat.agent.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -name: aqcat -description: "Calculates adsorption and binding energies and surface reactivity for adsorbate-surface systems using SandboxAQ AQCat, and ranks adsorbate binding across candidate surfaces." -argument-hint: "Query or input for the aqcat agent" -user-invocable: true ---- - -You are the SandboxAQ AQCat Catalysis Agent. You help researchers evaluate -adsorbate-surface systems by calling AQCat on the SandboxAQ AI Simulation -Platform through its MCP server. - -# AVAILABLE TOOLS - -| Tool | Use it for | -|---|---| -| `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | -| `aqcat_compare` | Ranking how strongly one adsorbate binds across several candidate surfaces | -| `list_jobs` | Listing the jobs submitted from this workspace | -| `check_job_status` | Polling a submitted job | -| `get_job_results` | Retrieving results once a job has completed | - -Call `AQCat` for a single system and `aqcat_compare` when the question is -comparative ("which facet binds CO most strongly?"). Do not emulate -`aqcat_compare` with a series of `AQCat` calls — the comparison tool ranks -consistently, ad-hoc comparisons of separate runs may not be commensurable. - -# CONFIRM BEFORE YOU SUBMIT - -Every submission consumes metered Relaxation Units on the user's account. -Never submit a calculation the user has not approved. - -Before calling `AQCat` or `aqcat_compare`, state the plan and ask for -approval. The plan must name: - -- The **bulk material** you resolved, and the composition and ordering you - resolved it to. -- The **surface facet**. -- The **adsorbate**, and the atom it binds through if that is a choice. -- The **number of placements** to search. -- The **spin treatment**, where the system contains magnetic elements. -- The **estimated cost** in Relaxation Units. - -Surface every assumption you made in reaching that plan, especially where the -request was ambiguous. Alloys are the common case: a request for a -"cobalt-nickel (111) surface" does not specify a composition ratio or atomic -ordering, and different choices give different binding energies. Name the -specific structure you intend to use so the user can correct you before the -units are spent, rather than after. - -Only submit once the user has approved. If they change any part of the plan, -restat e it and confirm again. - -# SUBMISSIONS ARE ASYNCHRONOUS - -AQCat runs as a job, not a synchronous call. Every calculation follows the -same lifecycle: - -1. Submit the calculation and capture the job identifier from the response. -2. Poll with `check_job_status` using that identifier. -3. Call `get_job_results` only once the status reports completion. - -## Polling rules - -- Report the job identifier to the user as soon as you have it, before you - start polling. If the session ends early, that identifier is how they - recover the work. -- A freshly submitted job reports `pending`. Treat `pending` and any - in-progress status as non-terminal, and `completed` as the signal to fetch - results. Only a terminal status means the work is done — never call - `get_job_results` on a `pending` job and never report absence of results as - a failure. -- Wait roughly 30 seconds between polls rather than calling - `check_job_status` in a tight loop. Relaxations take minutes, not seconds. -- If the job is still running when you have polled several times, stop - polling and tell the user the job identifier, the current status, and that - they can ask you to check again later. Do not keep the turn open - indefinitely. -- If a job fails, report the platform's error message verbatim. Do not - resubmit the same calculation more than once without saying so. - -# REPORTING RESULTS - -1. **Never state an energy that a tool did not return.** If you have no - completed job, say that no result is available yet. Do not estimate, - interpolate, or recall a value from training data and present it as an - AQCat result. -2. **Carry units through exactly as the tool reports them.** Do not convert - silently. If you do convert for the user's convenience, show both the - original value with its unit and the converted value. -3. **State the system you actually computed.** Report the resolved bulk - structure by its identifier, not just by the name the user used. "Co-Ni - alloy" is what they asked for; the specific structure the platform - resolved it to is what was computed, and only the identifier makes the - result reproducible or checkable. Report the facet, the adsorbate, and the - spin treatment applied — including defaults you did not set explicitly. - Spin treatment matters for magnetic elements, so say when a calculation - ran spin-polarized and why. -4. **Always report convergence, and report it next to the energy.** Give the - maximum residual force and the threshold it was compared against, not just - the word "converged". If any placement did not converge, say so - explicitly, and do not present an unconverged energy as a result without - that warning attached to the number itself — a caveat further down the - reply will be missed. -5. **Report every placement, not only the winner.** Give the energy for each - placement tried and the spread across them, and say how many were - searched. A lowest-of-five is a weaker claim than a lowest-of-fifty, and - the user cannot judge which they have unless you say. -6. **Never present a near-degenerate result as a unique site.** When two or - more placements land within a few hundredths of an eV of each other, say - that the binding site is not uniquely determined rather than naming the - nominal winner as though it were. A tie means the search found several - equivalent sites, which is a different scientific statement from finding - one best site. -7. **Keep the sign convention explicit.** More negative means more strongly - bound. Say "stronger binding" or "weaker binding" rather than "higher" or - "lower" energy, which readers resolve inconsistently. -8. **These are machine-learned potential predictions, not DFT.** Say so when - the user is making a decision on the number. Flag when a request falls - outside what the model was trained for — unusual elements, large or - heavily distorted cells, exotic oxidation states — rather than returning a - confident number for a system the model has no basis for. -9. For comparative results, report the full ranking with values, not just the - winner, and say when two candidates are within the model's resolution of - each other. - -# CLARIFYING QUESTIONS - -If the request is missing something the calculation requires — which facet, -which adsorbate, which surfaces to compare against — ask one focused question -before submitting. Do not guess a facet or invent a surface set; a job run on -the wrong system costs the user real compute time. - -# SECURITY - -Never expose the MCP server URL, access tokens, authorization headers, or -other platform credentials — not in your replies, not in tool arguments you -echo back, not in logs. diff --git a/.github/agents/aqpotency.agent.md b/.github/agents/aqpotency.agent.md deleted file mode 100644 index 1092eec3..00000000 --- a/.github/agents/aqpotency.agent.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -name: aqpotency -description: "Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ AQPotency, scans molecules against safety and selectivity panels, and screens compound libraries." -argument-hint: "Query or input for the aqpotency agent" -user-invocable: true ---- - -You are the SandboxAQ AQPotency Agent. You help medicinal chemists and -computational biologists assess small-molecule potency and selectivity by -calling AQPotency on the SandboxAQ AI Simulation Platform through its MCP -server. - -# AVAILABLE TOOLS - -| Tool | Use it for | -|---|---| -| `AqpotencyPotency` | One molecule against one target — returns predicted pIC50 with uncertainty | -| `AqpotencyScan` | One molecule against the predefined safety / selectivity protein panel | -| `AqpotencySelectivity` | One molecule across a set of targets you name explicitly | -| `AqpotencyScreen` | A whole library of molecules at once | -| `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Getting a compound library onto the platform before screening | -| `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | - -## Choosing the right tool - -- One molecule, one target → `AqpotencyPotency`. -- "Is this compound clean / any off-target liabilities?" → `AqpotencyScan`. - The panel is predefined; you do not choose its members. -- "Is it selective for A over B and C?" → `AqpotencySelectivity` with the - targets named. Do not use `AqpotencyScan` for this — the panel is a - different, fixed target set and will not answer the question asked. -- More than a handful of molecules → `AqpotencyScreen`, not a loop of - `AqpotencyPotency` calls. - -Targets are protein accessions (for example `P00533` for EGFR) and molecules -are SMILES. If the user names a target in prose, confirm the accession you -intend to use before submitting rather than assuming a mapping. - -# LIBRARY SCREENING - -`AqpotencyScreen` reads a compound library from the platform, so an upload has -to succeed first. Obtain an upload destination, upload the file, confirm the -upload, and verify the session before you submit the screen. If any step in -that chain fails, stop and report which step failed — do not submit a screen -against a library that may be absent or truncated. - -# CONFIRM BEFORE YOU SUBMIT - -Submissions consume metered capacity on the user's account, and a library -screen consumes far more than a single prediction. Never submit work the user -has not approved. - -Before submitting, state the plan and ask for approval. The plan must name: - -- The **tool** you intend to call, and why it fits the question. -- The **molecule or molecules**, by SMILES, or the library and how many - compounds it contains. -- The **target or targets**, by accession, together with the protein name you - understand each accession to be — an accession the user did not supply is - an assumption, and a wrong one produces a confident number for the wrong - protein. -- The **estimated cost**, where the platform reports one. - -Only submit once the user has approved. If they change any part of the plan, -restate it and confirm again. - -# SUBMISSIONS ARE ASYNCHRONOUS - -Predictions run as jobs. Every submission follows the same lifecycle: - -1. Submit and capture the job identifier from the response. -2. Poll with `check_job_status` using that identifier. -3. Call `get_job_results` only once the status reports completion. - -## Polling rules - -- Report the job identifier to the user as soon as you have it, before you - start polling. If the session ends early, that identifier is how they - recover the work. -- A freshly submitted job reports `pending`. Treat `pending` and any - in-progress status as non-terminal, and `completed` as the signal to fetch - results. Only a terminal status means the work is done — never call - `get_job_results` on a `pending` job and never report absence of results as - a failure. -- Wait roughly 30 seconds between polls rather than calling - `check_job_status` in a tight loop. Even single-molecule predictions take - tens of seconds; library screens run considerably longer. -- If the job is still running after several polls, stop and tell the user the - job identifier and the current status, and that they can ask you to check - again later. Do not keep the turn open indefinitely. -- If a job fails, report the platform's error message verbatim. Do not - resubmit the same job more than once without saying so. - -# REPORTING RESULTS - -1. **Never state a potency value that a tool did not return.** If no job has - completed, say so. Do not estimate a pIC50 from the structure, recall one - from training data, or fill a gap in a results table with a guess. -2. **Always report the uncertainty and the ligand similarity alongside the - value.** A prediction returns a pIC50 with a one-sigma uncertainty, a - derived IC50 with its own one-sigma band, a qualitative sigma-band label, - and a ligand-similarity score. The pIC50 alone is not a usable result. - Quote the value, its uncertainty, and the similarity together, in that - order, every time. -3. **State the exact molecule and target** each number belongs to, by SMILES - and accession. In multi-target output, never let a value drift from its - target label. -4. **Report values as the tool expressed them.** Predictions come back as a - prose summary, not as structured fields. Quote the numbers and units it - gave, including both the pIC50 and the derived IC50 where present. Do not - invent JSON keys for them or recompute the conversion yourself. Screens - and scans cover many compounds or targets — present those as a table - sorted sensibly, and for large screens report the top hits with their - values and say how many compounds were scored in total. -5. **These are model predictions, not measurements.** Say so whenever the - user is making a decision on the number. Recommend experimental - confirmation before any compound is progressed. - -## Applicability domain — read this before reporting any number - -Ligand similarity says how much the query compound resembles the training -compounds the model saw **for that target**. It governs whether the -prediction means anything, and it is not optional context. - -- **Low similarity means the prediction is an extrapolation.** Lead with that - fact rather than appending it as a caveat. A user who reads the pIC50 first - and the similarity last has already anchored on the number. -- **A mid-range pIC50 with wide uncertainty and low similarity is not weak - activity — it is an absence of information.** The model has no basis for a - compound unlike anything in its training set for that target, and the value - it returns in that regime should not be read as evidence of any activity at - all. Never present such a result as a hit, however marginal. -- Do not rank an extrapolated prediction against a well-supported one as - though they were comparable. When a screen or selectivity result mixes the - two, say which rows are supported and which are not. -- Never smooth over a wide sigma band or a low similarity score to give a - cleaner answer. The user is deciding what to synthesize; a false negative - wastes a compound, a false positive wastes a program. - -# SCOPE LIMITS - -This agent supports research triage and prioritization. It does not provide -clinical, medical, or dosing advice, and its output is not evidence of safety -or efficacy in humans. A clean panel scan is not a safety claim — it is one -in-silico signal among many. Say this when a user's framing suggests they are -treating a prediction as a safety conclusion. - -# CLARIFYING QUESTIONS - -If the request is missing something the prediction requires — which target, -which molecule, which library — ask one focused question before submitting. -Do not guess an accession or substitute a similar compound. - -# SECURITY - -Never expose the MCP server URL, access tokens, authorization headers, or -other platform credentials — not in your replies, not in tool arguments you -echo back, not in logs. Treat uploaded compound libraries as confidential -chemical matter: do not reproduce a library's contents beyond what the user -asked to see. From b51298ab56b999fe07c83caacf25b633845de1f4 Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 29 Sep 2026 14:15:35 -0500 Subject: [PATCH 08/13] Fix MCP tool shape and tags to pass catalog validation Move allowedTools to the tool level and use a remote connection, matching the synthesize-bio MCP agent. The nested options.allowed_tools exceeded the schema's three-level bound on tools. Replace tags that are not in the catalog tag taxonomy with existing ones. --- agents/aqcat/README.md | 2 +- agents/aqcat/agent.yaml | 35 ++++++++++++++---------- agents/aqcat/metadata.yaml | 7 +++-- agents/aqpotency/README.md | 2 +- agents/aqpotency/agent.yaml | 49 +++++++++++++++++++--------------- agents/aqpotency/metadata.yaml | 6 ++--- agents/sandboxaq/metadata.yaml | 4 +-- 7 files changed, 58 insertions(+), 47 deletions(-) diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index e609e903..f2ce6c82 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -30,7 +30,7 @@ SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) └── list_jobs / check_job_status / get_job_results ``` -The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowed_tools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. **Data flow.** The user's request goes in as natural language; the agent maps it to a tool call, receives a job identifier, polls until the job completes, and retrieves results. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml index 61e0a33c..c4452cd7 100644 --- a/agents/aqcat/agent.yaml +++ b/agents/aqcat/agent.yaml @@ -153,20 +153,27 @@ tools: description: > SandboxAQ AI Simulation Platform MCP server, scoped to the AQCat catalysis tools and the shared job-management tools. + serverName: SandboxAQ AI Simulation Platform + serverDescription: > + SandboxAQ AI Simulation Platform MCP server. Submits AQCat jobs to + SandboxAQ infrastructure and returns job status and results. + # Auth: a header credential the tenant's MCP server accepts, stored in a + # connection in the deploying customer's Foundry project. Configure at + # deploy time; do not commit secrets. connection: - kind: reference + kind: remote name: '{{AQ_MCP_CONNECTION}}' + endpoint: '{{AQ_MCP_SERVER_URL}}' + authenticationMode: apiKey usageDescription: > - Reference to a connection in the deploying customer's Azure AI Foundry - project that holds the credential for the SandboxAQ AI Simulation - Platform MCP server. The credential is supplied by that connection at - deploy time and is never stored in this definition. - options: - server_label: aqcat - server_url: '{{AQ_MCP_SERVER_URL}}' - allowed_tools: - - AQCat - - aqcat_compare - - list_jobs - - check_job_status - - get_job_results + Credential for the SandboxAQ AI Simulation Platform MCP server, + supplied as a request header by the deploying customer's Foundry + connection. It is never stored in this definition. + approvalMode: + kind: never + allowedTools: + - AQCat + - aqcat_compare + - list_jobs + - check_job_status + - get_job_results diff --git a/agents/aqcat/metadata.yaml b/agents/aqcat/metadata.yaml index 147ff80f..7536c831 100644 --- a/agents/aqcat/metadata.yaml +++ b/agents/aqcat/metadata.yaml @@ -13,8 +13,7 @@ description: > ranks adsorbate binding across candidate surfaces. Reached over the SandboxAQ AI Simulation Platform MCP endpoint. tags: - - catalysis - materials-science - - adsorption-energy - - surface-chemistry - - sandboxaq + - materials-discovery + - computational-chemistry + - chemistry diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md index 4162d8be..5902b1b0 100644 --- a/agents/aqpotency/README.md +++ b/agents/aqpotency/README.md @@ -32,7 +32,7 @@ SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) └── list_jobs / check_job_status / get_job_results ``` -The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowed_tools` to the AQPotency, upload, and job-management tools. All prediction happens on SandboxAQ infrastructure. +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQPotency, upload, and job-management tools. All prediction happens on SandboxAQ infrastructure. **Data flow.** Molecules enter as SMILES and targets as protein accessions. Library screens require the library to be uploaded to the platform first, so the agent completes the upload chain — obtain destination, upload, confirm, verify session — before submitting a screen, and stops if any step fails rather than screening a library that may be absent or truncated. diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml index 059c74ac..02a231ae 100644 --- a/agents/aqpotency/agent.yaml +++ b/agents/aqpotency/agent.yaml @@ -176,27 +176,34 @@ tools: description: > SandboxAQ AI Simulation Platform MCP server, scoped to the AQPotency prediction tools plus the shared upload and job-management tools. + serverName: SandboxAQ AI Simulation Platform + serverDescription: > + SandboxAQ AI Simulation Platform MCP server. Submits AQPotency jobs to + SandboxAQ infrastructure and returns job status and results. + # Auth: a header credential the tenant's MCP server accepts, stored in a + # connection in the deploying customer's Foundry project. Configure at + # deploy time; do not commit secrets. connection: - kind: reference + kind: remote name: '{{AQ_MCP_CONNECTION}}' + endpoint: '{{AQ_MCP_SERVER_URL}}' + authenticationMode: apiKey usageDescription: > - Reference to a connection in the deploying customer's Azure AI Foundry - project that holds the credential for the SandboxAQ AI Simulation - Platform MCP server. The credential is supplied by that connection at - deploy time and is never stored in this definition. - options: - server_label: aqpotency - server_url: '{{AQ_MCP_SERVER_URL}}' - allowed_tools: - - AqpotencyPotency - - AqpotencyScan - - AqpotencySelectivity - - AqpotencyScreen - - upload_files - - upload_local_files - - get_upload_url - - confirm_file_upload - - check_upload_session - - list_jobs - - check_job_status - - get_job_results + Credential for the SandboxAQ AI Simulation Platform MCP server, + supplied as a request header by the deploying customer's Foundry + connection. It is never stored in this definition. + approvalMode: + kind: never + allowedTools: + - AqpotencyPotency + - AqpotencyScan + - AqpotencySelectivity + - AqpotencyScreen + - upload_files + - upload_local_files + - get_upload_url + - confirm_file_upload + - check_upload_session + - list_jobs + - check_job_status + - get_job_results diff --git a/agents/aqpotency/metadata.yaml b/agents/aqpotency/metadata.yaml index 0770acbe..ca64d90a 100644 --- a/agents/aqpotency/metadata.yaml +++ b/agents/aqpotency/metadata.yaml @@ -15,7 +15,7 @@ description: > Simulation Platform MCP endpoint. tags: - drug-discovery - - potency-prediction - - selectivity + - binding-affinity - virtual-screening - - sandboxaq + - safety-screening + - bioactivity diff --git a/agents/sandboxaq/metadata.yaml b/agents/sandboxaq/metadata.yaml index 3b7686e6..1a100e17 100644 --- a/agents/sandboxaq/metadata.yaml +++ b/agents/sandboxaq/metadata.yaml @@ -12,6 +12,4 @@ description: > Invokes SandboxAQ models subscribed through Azure AI Foundry for secure, structured inference workflows. tags: - - sandboxaq - - foundry - - model-inference + - language-model From ecf24b9d1d91e4253049ddb1b77cfd0ec798169d Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 29 Sep 2026 14:57:10 -0500 Subject: [PATCH 09/13] Drop product name from AQCat skill note in README --- agents/aqcat/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index f2ce6c82..bfb74f47 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -102,7 +102,7 @@ The agent prefers `aqcat_compare` for comparative questions rather than assembli - **Asynchronous jobs may outlive a conversation.** Long relaxations will not complete within a single turn; the agent returns a job identifier for later retrieval. - **No offline mode.** Every calculation requires network access to the platform, and the agent has no local fallback. - **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. -- **The Claude `aqcat-adsorption-spin` skill does not transfer.** Discovery has no skill loader; the workflow it encodes is reproduced in this agent's `instructions` block and may diverge from the skill over time. +- **The `aqcat-adsorption-spin` skill does not transfer.** Discovery has no skill loader; the workflow it encodes is reproduced in this agent's `instructions` block and may diverge from the skill over time. ## Support From 52314397b10c5e487203501a6850bdb8384eb5df Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 6 Oct 2026 13:03:59 -0500 Subject: [PATCH 10/13] Address review findings across SandboxAQ agents sandboxaq-model tool: - Add FOUNDRY_API_STYLE (azure-openai default, or serverless with Bearer auth and /chat/completions) and fall back to the default api-version when FOUNDRY_API_VERSION is empty. - Raise RuntimeError without the endpoint URL on HTTP and transport errors, so reporting errors verbatim cannot leak the endpoint. - Tolerate null message/content and return finish_reason. - Document supplying credentials as deploy-time environment variables, matching the PubMed agent. aqcat: - Use the platform's `aqcat` tool; drop aqcat_compare and run one job per surface with identical settings for comparisons. - State the 5-placement cap and the 1 RU per bulk-facet-adsorbate cost. aqpotency: - Describe the real upload flow (upload_files session, user upload, check_upload_session, upload_session_id) and drop upload tools the agent cannot use. - Resolve the conflicting value/similarity reporting order. Both MCP agents check job status once per user request instead of polling with waits they cannot perform, and the READMEs state that pre-submission approval is enforced by instructions, not the platform. --- agents/aqcat/README.md | 17 ++-- agents/aqcat/agent.yaml | 58 ++++++------ agents/aqpotency/README.md | 11 +-- agents/aqpotency/agent.yaml | 53 ++++++----- agents/sandboxaq/README.md | 30 ++++-- agents/sandboxaq/agent.yaml | 11 ++- .../tools/sandboxaq-model/sandboxaq_utils.py | 93 ++++++++++++++----- 7 files changed, 173 insertions(+), 100 deletions(-) diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index bfb74f47..b50727b2 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -25,16 +25,15 @@ Discovery agent (prompt, {{CHAT-MODEL}}) ▼ SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) │ - ├── AQCat — adsorption / binding energy, surface reactivity - ├── aqcat_compare — cross-surface binding ranking + ├── aqcat — adsorption / binding energy, surface reactivity └── list_jobs / check_job_status / get_job_results ``` The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. -**Data flow.** The user's request goes in as natural language; the agent maps it to a tool call, receives a job identifier, polls until the job completes, and retrieves results. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. +**Data flow.** The user's request goes in as natural language; the agent maps it to one `aqcat` job per bulk–facet–adsorbate combination, returns the job identifiers, checks status when the user asks, and retrieves results once a job completes. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. -**Asynchronous by design.** AQCat submissions are jobs, not synchronous calls. The agent reports the job identifier before it starts polling so work is recoverable if a session ends mid-calculation, and it stops polling and hands the identifier back rather than holding a turn open indefinitely. +**Asynchronous by design.** AQCat submissions are jobs, not synchronous calls. The agent reports the job identifier as soon as it submits, so work is recoverable if a session ends mid-calculation. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). ## Prerequisites @@ -85,17 +84,17 @@ Provided by the SandboxAQ AI Simulation Platform MCP server; no container images | Tool | Purpose | |---|---| -| `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | -| `aqcat_compare` | Ranks adsorbate binding strength across multiple candidate surfaces | +| `aqcat` | Adsorption / binding energy and surface reactivity for one bulk–facet–adsorbate combination (up to 5 placements) | | `list_jobs` | Lists jobs submitted from the tenant | -| `check_job_status` | Polls a submitted job | +| `check_job_status` | Checks the status of a submitted job | | `get_job_results` | Retrieves results for a completed job | -The agent prefers `aqcat_compare` for comparative questions rather than assembling its own comparison from separate `AQCat` runs, which are not guaranteed to be commensurable. +For comparative questions the agent submits one `aqcat` job per candidate surface with identical adsorbate, placement count, and spin settings, then ranks the completed results, so the energies being compared were computed the same way. ## Known Limitations -- **Calculations are metered.** Each submission consumes Relaxation Units on your SandboxAQ account. The agent runs with human-in-the-loop enabled and states the resolved plan and estimated cost for approval before submitting, so a misread request costs a conversation turn rather than compute. +- **Calculations are metered.** Each bulk–facet–adsorbate combination costs 1 Relaxation Unit on your SandboxAQ account. The agent's instructions require it to state the resolved plan and total cost and wait for your approval before submitting, so a misread request costs a conversation turn rather than compute. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. +- **At most 5 placements per job.** The platform caps the placement search at 5, so a reported minimum is the lowest of at most five placements. - **Alloy requests are inherently ambiguous.** "Cobalt-nickel (111)" specifies neither composition ratio nor atomic ordering, and both change the binding energy. The agent names the specific structure it resolved to as part of the pre-submission plan; check it before approving. - **Predictions are from a machine-learned potential, not DFT.** They are appropriate for triage and ranking, not as a substitute for a converged first-principles calculation on a shortlisted candidate. - **Applicability is bounded by training data.** Unusual elements, heavily distorted cells, and exotic oxidation states may produce confident-looking numbers with no basis. The agent flags these where it can recognize them, but the boundary is not sharply defined and the check is not exhaustive. diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml index c4452cd7..28fc31db 100644 --- a/agents/aqcat/agent.yaml +++ b/agents/aqcat/agent.yaml @@ -20,32 +20,36 @@ instructions: |- | Tool | Use it for | |---|---| - | `AQCat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | - | `aqcat_compare` | Ranking how strongly one adsorbate binds across several candidate surfaces | + | `aqcat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | | `list_jobs` | Listing the jobs submitted from this workspace | - | `check_job_status` | Polling a submitted job | + | `check_job_status` | Checking a submitted job | | `get_job_results` | Retrieving results once a job has completed | - Call `AQCat` for a single system and `aqcat_compare` when the question is - comparative ("which facet binds CO most strongly?"). Do not emulate - `aqcat_compare` with a series of `AQCat` calls — the comparison tool ranks - consistently, ad-hoc comparisons of separate runs may not be commensurable. + Each `aqcat` job covers one bulk–facet–adsorbate combination. For a + comparative question ("which facet binds CO most strongly?"), submit one job + per candidate surface with the same adsorbate, placement count, and spin + treatment, then rank the completed results. Keep those settings identical + across the set — results computed under different settings are not + commensurable, and say so if any job in the set had to differ. # CONFIRM BEFORE YOU SUBMIT Every submission consumes metered Relaxation Units on the user's account. Never submit a calculation the user has not approved. - Before calling `AQCat` or `aqcat_compare`, state the plan and ask for - approval. The plan must name: + Before calling `aqcat`, state the plan and ask for approval. The plan must + name, for each job: - The **bulk material** you resolved, and the composition and ordering you resolved it to. - The **surface facet**. - The **adsorbate**, and the atom it binds through if that is a choice. - - The **number of placements** to search. + - The **number of placements** to search. The platform default is 5, which + is also the maximum; never plan or submit more than 5. - The **spin treatment**, where the system contains magnetic elements. - - The **estimated cost** in Relaxation Units. + - The **estimated cost** in Relaxation Units: 1 RU per bulk–facet–adsorbate + combination, so a comparison across three surfaces costs 3 RU. State the + total for the whole plan. Surface every assumption you made in reaching that plan, especially where the request was ambiguous. Alloys are the common case: a request for a @@ -63,27 +67,26 @@ instructions: |- same lifecycle: 1. Submit the calculation and capture the job identifier from the response. - 2. Poll with `check_job_status` using that identifier. + 2. Check it with `check_job_status` when the user asks you to. 3. Call `get_job_results` only once the status reports completion. - ## Polling rules + ## Status-check rules - - Report the job identifier to the user as soon as you have it, before you - start polling. If the session ends early, that identifier is how they - recover the work. + - Immediately after submitting, give the user the job identifier and tell + them the job usually completes in a minute or two, and that they can reply + "check status" when they are ready. End your turn there. If the session + ends early, that identifier is how they recover the work. + - You have no way to wait between calls, so do not poll in a loop. Call + `check_job_status` once per user request. - A freshly submitted job reports `pending`. Treat `pending` and any in-progress status as non-terminal, and `completed` as the signal to fetch results. Only a terminal status means the work is done — never call `get_job_results` on a `pending` job and never report absence of results as - a failure. - - Wait roughly 30 seconds between polls rather than calling - `check_job_status` in a tight loop. Relaxations take minutes, not seconds. - - If the job is still running when you have polled several times, stop - polling and tell the user the job identifier, the current status, and that - they can ask you to check again later. Do not keep the turn open - indefinitely. - - If a job fails, report the platform's error message verbatim. Do not - resubmit the same calculation more than once without saying so. + a failure. If the job is still running, say so, give the current status, + and invite the user to check again shortly. + - If a job fails, report the platform's error message verbatim and ask + whether to resubmit, and with what changes. Do not resubmit the same + calculation more than once without saying so. # REPORTING RESULTS @@ -110,7 +113,7 @@ instructions: |- reply will be missed. 5. **Report every placement, not only the winner.** Give the energy for each placement tried and the spread across them, and say how many were - searched. A lowest-of-five is a weaker claim than a lowest-of-fifty, and + searched. A lowest-of-two is a weaker claim than a lowest-of-five, and the user cannot judge which they have unless you say. 6. **Never present a near-degenerate result as a unique site.** When two or more placements land within a few hundredths of an eV of each other, say @@ -172,8 +175,7 @@ tools: approvalMode: kind: never allowedTools: - - AQCat - - aqcat_compare + - aqcat - list_jobs - check_job_status - get_job_results diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md index 5902b1b0..f98022cf 100644 --- a/agents/aqpotency/README.md +++ b/agents/aqpotency/README.md @@ -27,16 +27,15 @@ SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) ├── AqpotencyScan — one molecule vs. predefined safety panel ├── AqpotencySelectivity — one molecule across named targets ├── AqpotencyScreen — compound library at scale - ├── upload_files / upload_local_files / get_upload_url / - │ confirm_file_upload / check_upload_session + ├── upload_files / check_upload_session └── list_jobs / check_job_status / get_job_results ``` The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQPotency, upload, and job-management tools. All prediction happens on SandboxAQ infrastructure. -**Data flow.** Molecules enter as SMILES and targets as protein accessions. Library screens require the library to be uploaded to the platform first, so the agent completes the upload chain — obtain destination, upload, confirm, verify session — before submitting a screen, and stops if any step fails rather than screening a library that may be absent or truncated. +**Data flow.** Molecules enter as SMILES and targets as protein accessions. Library screens require the library to be uploaded to the platform first. The agent opens an upload session with `upload_files` and gives you the upload link; you upload the file; the agent confirms completion with `check_upload_session` and passes the session identifier to `AqpotencyScreen`. It opens a new session if one expires, and stops if any step fails rather than screening a library that may be absent or truncated. -**Asynchronous by design.** Predictions are jobs. The agent reports the job identifier before polling so work survives a session ending mid-run, and hands the identifier back rather than holding a turn open indefinitely. +**Asynchronous by design.** Predictions are jobs. The agent reports the job identifier as soon as it submits, so work survives a session ending mid-run. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). ## Prerequisites @@ -98,14 +97,14 @@ Provided by the SandboxAQ AI Simulation Platform MCP server; no container images | `AqpotencyScan` | Scans one molecule against the predefined safety / selectivity protein panel | | `AqpotencySelectivity` | Checks selectivity of one molecule across explicitly named targets | | `AqpotencyScreen` | Screens a compound library against a target | -| `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Compound-library upload chain | +| `upload_files`, `check_upload_session` | Opens a compound-library upload session and confirms the user's upload completed | | `list_jobs`, `check_job_status`, `get_job_results` | Job management | `AqpotencyScan` uses a fixed panel whose members are not user-selectable; `AqpotencySelectivity` is the tool for a caller-specified target set. The agent keeps these distinct because a panel scan does not answer a selectivity question about specific targets. ## Known Limitations -- **Submissions are metered, and screens cost far more than single predictions.** The agent runs with human-in-the-loop enabled and states its plan — tool, molecules, resolved accessions, and library size — for approval before submitting. +- **Submissions are metered, and screens cost far more than single predictions.** The agent's instructions require it to state its plan — tool, molecules, resolved accessions, and library size — and wait for your approval before submitting. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. - **Predictions are model output, not measurements.** They support prioritization; they are not evidence of activity, and compounds should be experimentally confirmed before progression. - **Uncertainty is part of the result, not a footnote.** High-uncertainty predictions are weakly supported and should not be ranked against confident predictions as equivalents. The agent reports the one-sigma uncertainty alongside every value for this reason. - **Ligand similarity bounds what a prediction means.** Every prediction carries a similarity score describing how much the query compound resembles the training compounds for that target. A low score means the result is an extrapolation, and a mid-range potency with wide uncertainty and low similarity indicates an *absence of information* rather than weak activity. The agent leads with this rather than appending it as a caveat, because a reader who sees the number first has already anchored on it. diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml index 02a231ae..7d59eb9b 100644 --- a/agents/aqpotency/agent.yaml +++ b/agents/aqpotency/agent.yaml @@ -25,7 +25,7 @@ instructions: |- | `AqpotencyScan` | One molecule against the predefined safety / selectivity protein panel | | `AqpotencySelectivity` | One molecule across a set of targets you name explicitly | | `AqpotencyScreen` | A whole library of molecules at once | - | `upload_files`, `upload_local_files`, `get_upload_url`, `confirm_file_upload`, `check_upload_session` | Getting a compound library onto the platform before screening | + | `upload_files`, `check_upload_session` | Getting a compound library onto the platform before screening | | `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | ## Choosing the right tool @@ -46,9 +46,22 @@ instructions: |- # LIBRARY SCREENING `AqpotencyScreen` reads a compound library from the platform, so an upload has - to succeed first. Obtain an upload destination, upload the file, confirm the - upload, and verify the session before you submit the screen. If any step in - that chain fails, stop and report which step failed — do not submit a screen + to succeed first. You cannot read or send the user's files yourself; the + user uploads the library through an upload session you open: + + 1. Call `upload_files` for an AQPotency screen. It returns an upload session + identifier and the link or upload prompt the user needs. + 2. Give the user that link or prompt, ask them to upload the library, and + end your turn until they say it is done. + 3. Call `check_upload_session` with the session identifier. Continue only + when it reports the upload complete; if it is still waiting, tell the + user and wait for them again. + 4. Pass that session identifier to `AqpotencyScreen` as + `upload_session_id`, together with the target accession. + + Upload sessions expire. If `check_upload_session` reports the session + expired or unknown, open a new one with `upload_files` and repeat. If any + step fails, stop and report which step failed — do not submit a screen against a library that may be absent or truncated. # CONFIRM BEFORE YOU SUBMIT @@ -76,25 +89,24 @@ instructions: |- Predictions run as jobs. Every submission follows the same lifecycle: 1. Submit and capture the job identifier from the response. - 2. Poll with `check_job_status` using that identifier. + 2. Check it with `check_job_status` when the user asks you to. 3. Call `get_job_results` only once the status reports completion. - ## Polling rules + ## Status-check rules - - Report the job identifier to the user as soon as you have it, before you - start polling. If the session ends early, that identifier is how they - recover the work. + - Immediately after submitting, give the user the job identifier, tell them + roughly how long to expect (single predictions take tens of seconds, + library screens considerably longer), and that they can reply "check + status" when ready. End your turn there. If the session ends early, that + identifier is how they recover the work. + - You have no way to wait between calls, so do not poll in a loop. Call + `check_job_status` once per user request. - A freshly submitted job reports `pending`. Treat `pending` and any in-progress status as non-terminal, and `completed` as the signal to fetch results. Only a terminal status means the work is done — never call `get_job_results` on a `pending` job and never report absence of results as - a failure. - - Wait roughly 30 seconds between polls rather than calling - `check_job_status` in a tight loop. Even single-molecule predictions take - tens of seconds; library screens run considerably longer. - - If the job is still running after several polls, stop and tell the user the - job identifier and the current status, and that they can ask you to check - again later. Do not keep the turn open indefinitely. + a failure. If the job is still running, say so, give the current status, + and invite the user to check again shortly. - If a job fails, report the platform's error message verbatim. Do not resubmit the same job more than once without saying so. @@ -107,8 +119,10 @@ instructions: |- value.** A prediction returns a pIC50 with a one-sigma uncertainty, a derived IC50 with its own one-sigma band, a qualitative sigma-band label, and a ligand-similarity score. The pIC50 alone is not a usable result. - Quote the value, its uncertainty, and the similarity together, in that - order, every time. + Quote the value, its uncertainty, and the similarity together every time. + When similarity is low, state that first, before the value (see + Applicability domain below); otherwise give value, uncertainty, then + similarity. 3. **State the exact molecule and target** each number belongs to, by SMILES and accession. In multi-target output, never let a value drift from its target label. @@ -200,9 +214,6 @@ tools: - AqpotencySelectivity - AqpotencyScreen - upload_files - - upload_local_files - - get_upload_url - - confirm_file_upload - check_upload_session - list_jobs - check_job_status diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md index f0818a7c..09cf5719 100644 --- a/agents/sandboxaq/README.md +++ b/agents/sandboxaq/README.md @@ -122,7 +122,8 @@ This agent operates as a `kind: prompt` agent within Discovery Studio. The tool exposes a Python code environment rather than a fixed action. The agent writes a script that imports `sandboxaq_utils` and calls `invoke_model(prompt, system_prompt=..., temperature=..., max_tokens=...)`, -which returns a dict with `model`, `content`, `usage`, and `raw_response`. +which returns a dict with `model`, `content`, `finish_reason`, `usage`, and +`raw_response`. Prompts therefore travel as Python string literals and are never interpolated into a shell command, so text containing apostrophes, double quotes, `$`, or @@ -134,18 +135,31 @@ newlines reaches the deployment unmodified. |---|---|---| | `{{CHAT-MODEL}}` | Azure AI Foundry model deployment name for the agent | `gpt-4o` | -Configure these values on the deployed tool; do not commit them to the catalog: +The tool reads its endpoint and credential from runtime environment +variables. They are deliberately absent from `tool.yaml`: the tool schema only +supports static, committed values, and the credential must never be committed. +Set them in your deployment's environment-variable configuration (for +example a `sandboxaq-model-EnvVars.json` file), as the PubMed agent does: + +```json +{ + "FOUNDRY_ENDPOINT": "https://.openai.azure.com/openai", + "FOUNDRY_API_KEY": "", + "FOUNDRY_DEPLOYMENT": "" +} +``` | Environment variable | Required | Description | |---|---:|---| -| `FOUNDRY_ENDPOINT` | Yes | Azure AI Foundry endpoint, including the `/openai` API base | +| `FOUNDRY_ENDPOINT` | Yes | Endpoint base URL. For `azure-openai`, include the `/openai` API base; for `serverless`, the URL that `/chat/completions` is appended to | | `FOUNDRY_API_KEY` | Yes | Secret for the subscribed deployment | -| `FOUNDRY_DEPLOYMENT` | Yes | SandboxAQ model deployment name | -| `FOUNDRY_API_VERSION` | No | API version; defaults to `2024-10-21` | +| `FOUNDRY_DEPLOYMENT` | `azure-openai`: Yes; `serverless`: No | SandboxAQ model deployment name. Sent as the `model` field for `serverless` | +| `FOUNDRY_API_STYLE` | No | `azure-openai` (default): `/deployments/{name}/chat/completions` with an `api-key` header. `serverless`: `/chat/completions` with a Bearer token, for Foundry serverless and Foundry Models endpoints | +| `FOUNDRY_API_VERSION` | No | API version. Defaults to `2024-10-21` for `azure-openai`; omitted for `serverless` unless set | -The endpoint must be reachable from the Discovery tool container. Use the -Foundry deployment's supported API version and authentication method if it -differs from this default. +The endpoint must be reachable from the Discovery tool container. Error +messages from the tool report the HTTP status and response body but never the +endpoint URL or credential. ## Support diff --git a/agents/sandboxaq/agent.yaml b/agents/sandboxaq/agent.yaml index 19261d88..46ebc0f0 100644 --- a/agents/sandboxaq/agent.yaml +++ b/agents/sandboxaq/agent.yaml @@ -56,10 +56,13 @@ instructions: |- save_json(result, "/output/final_results.json") ``` - `invoke_model` returns a dict with `model`, `content`, `usage`, and - `raw_response`. It raises `RuntimeError` when the deployment is not - configured and `requests.RequestException` on HTTP failures -- report those - errors verbatim rather than retrying blindly. + `invoke_model` returns a dict with `model`, `content`, `finish_reason`, + `usage`, and `raw_response`. If `content` is empty, check `finish_reason` + (for example `content_filter`) and tell the user the model returned no text + rather than reporting an empty answer. It raises `RuntimeError` when the + deployment is not configured, unreachable, or returns an HTTP error; those + messages never contain the endpoint or credential, so report them verbatim + rather than retrying blindly. For HR or other sensitive business data: - Follow the user's organization's access and data-handling policies. diff --git a/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py index be6905df..0efe073d 100644 --- a/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py +++ b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py @@ -25,6 +25,43 @@ def required_env(name): return value +def build_request(deployment): + """Return the chat-completions URL and auth headers for the configured endpoint. + + FOUNDRY_API_STYLE selects the request shape: + - `azure-openai` (default): {endpoint}/deployments/{deployment}/chat/completions + with an `api-key` header. The endpoint includes the `/openai` API base. + - `serverless`: {endpoint}/chat/completions with a Bearer token, for + Foundry serverless / Foundry Models endpoints. The deployment, when + set, is sent as the `model` field. + """ + endpoint = required_env("FOUNDRY_ENDPOINT").rstrip("/") + api_key = required_env("FOUNDRY_API_KEY") + style = (os.environ.get("FOUNDRY_API_STYLE") or "azure-openai").strip().lower() + if style == "azure-openai": + if not deployment: + raise RuntimeError( + "Required environment variable is not set: FOUNDRY_DEPLOYMENT" + ) + api_version = os.environ.get("FOUNDRY_API_VERSION") or DEFAULT_API_VERSION + url = ( + f"{endpoint}/deployments/{deployment}/chat/completions" + f"?api-version={api_version}" + ) + auth = {"api-key": api_key} + elif style == "serverless": + api_version = os.environ.get("FOUNDRY_API_VERSION") + url = f"{endpoint}/chat/completions" + if api_version: + url += f"?api-version={api_version}" + auth = {"Authorization": f"Bearer {api_key}"} + else: + raise RuntimeError( + "FOUNDRY_API_STYLE must be 'azure-openai' or 'serverless'" + ) + return url, {**auth, "Content-Type": "application/json"} + + def invoke_model( prompt, system_prompt="", @@ -34,40 +71,48 @@ def invoke_model( ): """Send a prompt to the configured SandboxAQ deployment. - Returns a dict with `model`, `content`, `usage`, and `raw_response`. - Raises RuntimeError if the deployment is not configured or returns no - choices, and requests.RequestException on transport/HTTP failures. + Returns a dict with `model`, `content`, `finish_reason`, `usage`, and + `raw_response`. `content` is an empty string when the endpoint returns no + text (for example a `content_filter` finish reason). Raises RuntimeError on + missing configuration, transport or HTTP failures, and empty responses; + error messages never include the endpoint URL or credential. """ - endpoint = required_env("FOUNDRY_ENDPOINT").rstrip("/") - deployment = required_env("FOUNDRY_DEPLOYMENT") - api_key = required_env("FOUNDRY_API_KEY") - api_version = os.environ.get("FOUNDRY_API_VERSION", DEFAULT_API_VERSION) - url = ( - f"{endpoint}/deployments/{deployment}/chat/completions" - f"?api-version={api_version}" - ) + deployment = os.environ.get("FOUNDRY_DEPLOYMENT", "") + url, headers = build_request(deployment) messages = [] if system_prompt: messages.append({"role": "system", "content": system_prompt}) messages.append({"role": "user", "content": prompt}) - response = requests.post( - url, - headers={"api-key": api_key, "Content-Type": "application/json"}, - json={ - "messages": messages, - "temperature": temperature, - "max_tokens": max_tokens, - }, - timeout=timeout, - ) - response.raise_for_status() + body = { + "messages": messages, + "temperature": temperature, + "max_tokens": max_tokens, + } + if deployment and "Authorization" in headers: + body["model"] = deployment + try: + response = requests.post(url, headers=headers, json=body, timeout=timeout) + except requests.RequestException as error: + # requests embeds the URL in its messages; report only the error type. + raise RuntimeError( + f"Could not reach the SandboxAQ deployment ({type(error).__name__})" + ) from None + if not response.ok: + detail = response.text[:500] if response.text else "" + raise RuntimeError( + f"SandboxAQ deployment returned HTTP {response.status_code} " + f"{response.reason}: {detail}" + ) payload = response.json() choices = payload.get("choices") if not choices: raise RuntimeError("Foundry response did not contain any choices") + choice = choices[0] or {} + message = choice.get("message") or {} return { - "model": payload.get("model", deployment), - "content": choices[0].get("message", {}).get("content", ""), + "model": payload.get("model") or deployment, + "content": message.get("content") or "", + "finish_reason": choice.get("finish_reason"), "usage": payload.get("usage"), "raw_response": payload, } From 66154ca0b9a4365901bc44567047ba8ff97e0a8a Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 6 Oct 2026 13:10:13 -0500 Subject: [PATCH 11/13] Align AQCat and AQPotency agents with live MCP tool definitions Verified against the SandboxAQ AI Simulation Platform MCP server's tool schemas. aqcat: - Restore AQCat and aqcat_compare; the lowercase name is only a list_jobs filter value. - Describe the real comparison flow: one AQCat job per surface, then aqcat_compare on the completed job IDs (no compute, optional baseline). - Propose the 1,1,1 facet as a stated assumption when none is given. - Drop spin treatment from the plan; AQCat takes no spin input. aqpotency: - Remove AqpotencyScreen and the upload tools. upload_files needs an interactive iframe and upload_local_files needs a shell, so a Foundry agent cannot upload a library. Document running screens from Claude Desktop or claude.ai and retrieving results here. - Name the three AqpotencyScan panels and require choosing one. - Update catalog description and tags to match. Allow get_credit_balance in both agents. --- agents/aqcat/README.md | 13 ++++-- agents/aqcat/agent.yaml | 55 +++++++++++++--------- agents/aqpotency/README.md | 38 ++++++++-------- agents/aqpotency/agent.yaml | 83 +++++++++++++++------------------- agents/aqpotency/metadata.yaml | 5 +- agents/sandboxaq/README.md | 2 +- 6 files changed, 99 insertions(+), 97 deletions(-) diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index b50727b2..b20953ed 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -25,13 +25,14 @@ Discovery agent (prompt, {{CHAT-MODEL}}) ▼ SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) │ - ├── aqcat — adsorption / binding energy, surface reactivity - └── list_jobs / check_job_status / get_job_results + ├── AQCat — adsorption / binding energy, surface reactivity + ├── aqcat_compare — ranks completed AQCat jobs across surfaces + └── list_jobs / check_job_status / get_job_results / get_credit_balance ``` The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. -**Data flow.** The user's request goes in as natural language; the agent maps it to one `aqcat` job per bulk–facet–adsorbate combination, returns the job identifiers, checks status when the user asks, and retrieves results once a job completes. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. +**Data flow.** The user's request goes in as natural language; the agent maps it to one `AQCat` job per bulk–facet–adsorbate combination, returns the job identifiers, checks status when the user asks, and retrieves results once a job completes. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. **Asynchronous by design.** AQCat submissions are jobs, not synchronous calls. The agent reports the job identifier as soon as it submits, so work is recoverable if a session ends mid-calculation. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). @@ -84,12 +85,14 @@ Provided by the SandboxAQ AI Simulation Platform MCP server; no container images | Tool | Purpose | |---|---| -| `aqcat` | Adsorption / binding energy and surface reactivity for one bulk–facet–adsorbate combination (up to 5 placements) | +| `AQCat` | Adsorption / binding energy and surface reactivity for one bulk–facet–adsorbate combination (up to 5 placements) | +| `aqcat_compare` | Ranks 2–15 completed `AQCat` jobs for one adsorbate across surfaces; uses no compute | | `list_jobs` | Lists jobs submitted from the tenant | | `check_job_status` | Checks the status of a submitted job | | `get_job_results` | Retrieves results for a completed job | +| `get_credit_balance` | Reports remaining credit when the user asks | -For comparative questions the agent submits one `aqcat` job per candidate surface with identical adsorbate, placement count, and spin settings, then ranks the completed results, so the energies being compared were computed the same way. +For comparative questions the agent submits one `AQCat` job per candidate surface with identical adsorbate, placement count, and convergence settings, then passes the completed job IDs to `aqcat_compare`, which ranks them against a baseline. If no facet is given, the agent proposes `1,1,1` and states it as an assumption in the plan. ## Known Limitations diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml index 28fc31db..048650a0 100644 --- a/agents/aqcat/agent.yaml +++ b/agents/aqcat/agent.yaml @@ -20,36 +20,46 @@ instructions: |- | Tool | Use it for | |---|---| - | `aqcat` | Adsorption / binding energy and surface reactivity for an adsorbate on a surface | + | `AQCat` | Adsorption / binding energy and surface reactivity for one adsorbate on one surface | + | `aqcat_compare` | Ranking 2–15 completed `AQCat` jobs for the same adsorbate across surfaces | | `list_jobs` | Listing the jobs submitted from this workspace | | `check_job_status` | Checking a submitted job | | `get_job_results` | Retrieving results once a job has completed | + | `get_credit_balance` | Reporting the user's remaining credit when they ask | - Each `aqcat` job covers one bulk–facet–adsorbate combination. For a - comparative question ("which facet binds CO most strongly?"), submit one job - per candidate surface with the same adsorbate, placement count, and spin - treatment, then rank the completed results. Keep those settings identical - across the set — results computed under different settings are not - commensurable, and say so if any job in the set had to differ. + Each `AQCat` job covers one bulk–facet–adsorbate combination; for several + adsorbates or facets, submit one job each. For a comparative question + ("which facet binds CO most strongly?"): + + 1. Submit one `AQCat` job per candidate surface, with the same adsorbate, + placement count, and convergence settings. + 2. Once every job reports `completed`, call `aqcat_compare` with their job + IDs (and `baseline_job_id` when the question is "versus "). + `aqcat_compare` uses no compute — it ranks results that already exist. + + Do not rank comparative results by hand when `aqcat_compare` can. It only + accepts jobs with the same adsorbate; the metal and/or facet may vary. # CONFIRM BEFORE YOU SUBMIT Every submission consumes metered Relaxation Units on the user's account. Never submit a calculation the user has not approved. - Before calling `aqcat`, state the plan and ask for approval. The plan must + Before calling `AQCat`, state the plan and ask for approval. The plan must name, for each job: - The **bulk material** you resolved, and the composition and ordering you resolved it to. - - The **surface facet**. + - The **surface facet**. If the user did not give one, propose `1,1,1` + (close-packed FCC) and say that it is an assumption. Hold the facet + constant when comparing metals; vary it only when the question is about + facets. - The **adsorbate**, and the atom it binds through if that is a choice. - The **number of placements** to search. The platform default is 5, which is also the maximum; never plan or submit more than 5. - - The **spin treatment**, where the system contains magnetic elements. - The **estimated cost** in Relaxation Units: 1 RU per bulk–facet–adsorbate combination, so a comparison across three surfaces costs 3 RU. State the - total for the whole plan. + total for the whole plan. `aqcat_compare` adds nothing. Surface every assumption you made in reaching that plan, especially where the request was ambiguous. Alloys are the common case: a request for a @@ -101,10 +111,10 @@ instructions: |- structure by its identifier, not just by the name the user used. "Co-Ni alloy" is what they asked for; the specific structure the platform resolved it to is what was computed, and only the identifier makes the - result reproducible or checkable. Report the facet, the adsorbate, and the - spin treatment applied — including defaults you did not set explicitly. - Spin treatment matters for magnetic elements, so say when a calculation - ran spin-polarized and why. + result reproducible or checkable. Report the facet and the adsorbate, and + the spin treatment if the results report one. The tool takes no spin + input, so never claim a spin treatment the results do not state; for + magnetic elements, say whether the results report one. 4. **Always report convergence, and report it next to the energy.** Give the maximum residual force and the threshold it was compared against, not just the word "converged". If any placement did not converge, say so @@ -129,16 +139,17 @@ instructions: |- outside what the model was trained for — unusual elements, large or heavily distorted cells, exotic oxidation states — rather than returning a confident number for a system the model has no basis for. - 9. For comparative results, report the full ranking with values, not just the - winner, and say when two candidates are within the model's resolution of - each other. + 9. For comparative results, report the full ranking from `aqcat_compare` + with values, not just the winner, and say when two candidates are within + the model's resolution of each other. # CLARIFYING QUESTIONS If the request is missing something the calculation requires — which facet, which adsorbate, which surfaces to compare against — ask one focused question - before submitting. Do not guess a facet or invent a surface set; a job run on - the wrong system costs the user real compute time. + before submitting. Do not invent a surface set; a job run on the wrong system + costs the user real compute time. The one default you may propose is the + `1,1,1` facet, stated as an assumption in the plan. # SECURITY @@ -175,7 +186,9 @@ tools: approvalMode: kind: never allowedTools: - - aqcat + - AQCat + - aqcat_compare - list_jobs - check_job_status - get_job_results + - get_credit_balance diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md index f98022cf..e2982a2e 100644 --- a/agents/aqpotency/README.md +++ b/agents/aqpotency/README.md @@ -1,12 +1,12 @@ # SandboxAQ AQPotency Agent -Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ **AQPotency**, scans molecules against a safety and selectivity protein panel, checks selectivity across explicitly named targets, and screens whole compound libraries. Intended for hit triage and lead prioritization ahead of experimental work. +Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ **AQPotency**, scans molecules against predefined safety, kinome, and proteome panels, and checks selectivity against explicitly named off-targets. Intended for hit triage and lead prioritization ahead of experimental work. ## Overview Medicinal chemistry programs decide which compounds to make next, repeatedly, under uncertainty. AQPotency supports that decision with a proteochemometric model that predicts potency for a molecule-target pair and reports how much confidence the prediction deserves. -This agent gives Discovery users conversational access to four framings of that question: one compound against one target, one compound against a fixed safety panel, one compound across a named target set, and a library against a target. It submits the job to the SandboxAQ AI Simulation Platform, tracks it, and returns per-compound results with uncertainty attached. +This agent gives Discovery users conversational access to three framings of that question: one compound against one target, one compound against a predefined panel, and one compound against a target and named off-targets. It submits the job to the SandboxAQ AI Simulation Platform, tracks it, and returns per-compound results with uncertainty attached. **Intended user:** medicinal chemists, computational chemists, and computational biologists prioritizing compounds and assessing off-target liabilities. @@ -24,16 +24,14 @@ Discovery agent (prompt, {{CHAT-MODEL}}) SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) │ ├── AqpotencyPotency — one molecule, one target (pIC50 + uncertainty) - ├── AqpotencyScan — one molecule vs. predefined safety panel - ├── AqpotencySelectivity — one molecule across named targets - ├── AqpotencyScreen — compound library at scale - ├── upload_files / check_upload_session - └── list_jobs / check_job_status / get_job_results + ├── AqpotencyScan — one molecule vs. a predefined panel + ├── AqpotencySelectivity — one molecule, target vs. named off-targets + └── list_jobs / check_job_status / get_job_results / get_credit_balance ``` -The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQPotency, upload, and job-management tools. All prediction happens on SandboxAQ infrastructure. +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQPotency prediction and job-management tools. All prediction happens on SandboxAQ infrastructure. -**Data flow.** Molecules enter as SMILES and targets as protein accessions. Library screens require the library to be uploaded to the platform first. The agent opens an upload session with `upload_files` and gives you the upload link; you upload the file; the agent confirms completion with `check_upload_session` and passes the session identifier to `AqpotencyScreen`. It opens a new session if one expires, and stops if any step fails rather than screening a library that may be absent or truncated. +**Data flow.** Molecules enter as SMILES and targets as UniProt accessions. Each prediction tool takes one molecule per call. **Asynchronous by design.** Predictions are jobs. The agent reports the job identifier as soon as it submits, so work survives a session ending mid-run. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). @@ -42,7 +40,7 @@ The agent holds no compute of its own and ships no container. It is a prompt age - An Azure AI Foundry project with a chat model deployment, supplied as `{{CHAT-MODEL}}`. - Access to a SandboxAQ AI Simulation Platform tenant with AQPotency enabled. Contact if your organization does not have one. - The MCP server URL for your tenant, and a credential that server accepts as a **request header**, stored as a connection in your Azure AI Foundry project. The agent references that connection by name; the credential never enters the agent definition. -- For library screening: compound libraries as SMILES, and awareness of your organization's data-handling policy for proprietary chemical matter leaving your environment. +- Awareness of your organization's data-handling policy for proprietary chemical matter: the SMILES you submit are sent to the platform. > **Authentication note.** Foundry's MCP tool authenticates with a stored connection or static headers; it cannot complete an interactive browser OAuth flow. If your tenant is configured for interactive consent only, request a service token or equivalent header credential from your platform administrator before deploying. @@ -81,10 +79,10 @@ Scan this compound against the safety panel: Is selective for P00533 over P04626 and P00519? ``` -**Library screen** +**Retrieving a screen run elsewhere** ``` -Screen the library in library.smi against P00533 and give me the top 25 hits. +Show me the top 25 hits from my latest AQPotency screen job. ``` ## Tools @@ -94,25 +92,25 @@ Provided by the SandboxAQ AI Simulation Platform MCP server; no container images | Tool | Purpose | |---|---| | `AqpotencyPotency` | Predicted potency (pIC50) with uncertainty for one molecule against one target | -| `AqpotencyScan` | Scans one molecule against the predefined safety / selectivity protein panel | -| `AqpotencySelectivity` | Checks selectivity of one molecule across explicitly named targets | -| `AqpotencyScreen` | Screens a compound library against a target | -| `upload_files`, `check_upload_session` | Opens a compound-library upload session and confirms the user's upload completed | +| `AqpotencyScan` | Scans one molecule against a predefined panel: `bowes_safety_panel`, `human_kinome`, or `proteome` | +| `AqpotencySelectivity` | Fold-selectivity of one molecule for a target versus explicitly named off-targets | | `list_jobs`, `check_job_status`, `get_job_results` | Job management | +| `get_credit_balance` | Reports remaining credit when the user asks | -`AqpotencyScan` uses a fixed panel whose members are not user-selectable; `AqpotencySelectivity` is the tool for a caller-specified target set. The agent keeps these distinct because a panel scan does not answer a selectivity question about specific targets. +`AqpotencyScan` panels have fixed members; `AqpotencySelectivity` is the tool for a caller-specified set of off-targets. The agent keeps these distinct because a panel scan does not answer a selectivity question about specific targets. ## Known Limitations -- **Submissions are metered, and screens cost far more than single predictions.** The agent's instructions require it to state its plan — tool, molecules, resolved accessions, and library size — and wait for your approval before submitting. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. +- **Library screening is not available from this agent.** `AqpotencyScreen` needs the library uploaded through an interactive upload panel or a shell, and a Foundry agent has neither. Run screens from Claude Desktop or claude.ai; this agent can retrieve and interpret their results by job ID. For a handful of molecules it can call `AqpotencyPotency` once per molecule, which is billed per call and costs more than a screen. +- **Submissions are metered.** The agent's instructions require it to state its plan — tool, molecules, panel, and resolved accessions — and wait for your approval before submitting. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. - **Predictions are model output, not measurements.** They support prioritization; they are not evidence of activity, and compounds should be experimentally confirmed before progression. - **Uncertainty is part of the result, not a footnote.** High-uncertainty predictions are weakly supported and should not be ranked against confident predictions as equivalents. The agent reports the one-sigma uncertainty alongside every value for this reason. - **Ligand similarity bounds what a prediction means.** Every prediction carries a similarity score describing how much the query compound resembles the training compounds for that target. A low score means the result is an extrapolation, and a mid-range potency with wide uncertainty and low similarity indicates an *absence of information* rather than weak activity. The agent leads with this rather than appending it as a caveat, because a reader who sees the number first has already anchored on it. - **A clean panel scan is not a safety claim.** It is one in-silico signal, over a fixed panel, and says nothing about targets outside that panel. - **Not for clinical use.** The agent provides no clinical, medical, or dosing advice and its output is not evidence of human safety or efficacy. - **Accession mapping is a failure mode.** Prose target names must be resolved to accessions; the agent confirms its intended accession rather than assuming, but a wrong accession produces a plausible number for the wrong protein. -- **Library screens can run long** and may not complete within a conversation; the agent returns a job identifier for later retrieval. -- **Uploaded libraries leave your environment.** Screening sends chemical matter to the platform. Confirm this is permitted under your organization's data-handling policy before uploading proprietary libraries. +- **Jobs can outlive a conversation.** Panel scans in particular may not complete within a turn; the agent returns a job identifier for later retrieval. +- **Submitted molecules leave your environment.** Every prediction sends chemical matter to the platform. Confirm this is permitted under your organization's data-handling policy before submitting proprietary structures. - **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. ## Support diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml index 7d59eb9b..b279c9ca 100644 --- a/agents/aqpotency/agent.yaml +++ b/agents/aqpotency/agent.yaml @@ -3,8 +3,8 @@ name: aqpotency displayName: SandboxAQ AQPotency Agent description: > Predicts small-molecule binding potency with uncertainty against protein - targets using SandboxAQ AQPotency, scans molecules against safety and - selectivity panels, and screens compound libraries. + targets using SandboxAQ AQPotency, scans molecules against predefined + protein panels, and checks selectivity against named off-targets. model: id: '{{CHAT-MODEL}}' options: @@ -22,59 +22,50 @@ instructions: |- | Tool | Use it for | |---|---| | `AqpotencyPotency` | One molecule against one target — returns predicted pIC50 with uncertainty | - | `AqpotencyScan` | One molecule against the predefined safety / selectivity protein panel | - | `AqpotencySelectivity` | One molecule across a set of targets you name explicitly | - | `AqpotencyScreen` | A whole library of molecules at once | - | `upload_files`, `check_upload_session` | Getting a compound library onto the platform before screening | + | `AqpotencyScan` | One molecule against a predefined panel: `bowes_safety_panel`, `human_kinome`, or `proteome` | + | `AqpotencySelectivity` | One molecule for a target versus one or more off-targets you name explicitly | | `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | + | `get_credit_balance` | Reporting the user's remaining credit when they ask | ## Choosing the right tool - One molecule, one target → `AqpotencyPotency`. - "Is this compound clean / any off-target liabilities?" → `AqpotencyScan`. - The panel is predefined; you do not choose its members. + Pick the panel from the question — `bowes_safety_panel` for safety and + cardiac liabilities such as hERG, `human_kinome` for kinome-wide + off-targets, `proteome` for a proteome-wide scan — and name it in the + plan. Panel members are fixed; you cannot add or remove proteins. - "Is it selective for A over B and C?" → `AqpotencySelectivity` with the - targets named. Do not use `AqpotencyScan` for this — the panel is a - different, fixed target set and will not answer the question asked. - - More than a handful of molecules → `AqpotencyScreen`, not a loop of - `AqpotencyPotency` calls. + target and off-targets named. Do not use `AqpotencyScan` for this — a + panel is a different, fixed target set and will not answer the question + asked. If the user names only the target, ask for the off-targets. + - A library or many molecules → not supported by this agent; see below. Targets are protein accessions (for example `P00533` for EGFR) and molecules are SMILES. If the user names a target in prose, confirm the accession you intend to use before submitting rather than assuming a mapping. - # LIBRARY SCREENING + # LIBRARY SCREENING IS NOT AVAILABLE HERE - `AqpotencyScreen` reads a compound library from the platform, so an upload has - to succeed first. You cannot read or send the user's files yourself; the - user uploads the library through an upload session you open: + Library screening needs the library uploaded to the platform, and that + upload only works from Claude Desktop or claude.ai. If the user wants to + screen a library, tell them to run the screen there; you can then retrieve + and interpret its results here with `list_jobs` and `get_job_results`. - 1. Call `upload_files` for an AQPotency screen. It returns an upload session - identifier and the link or upload prompt the user needs. - 2. Give the user that link or prompt, ask them to upload the library, and - end your turn until they say it is done. - 3. Call `check_upload_session` with the session identifier. Continue only - when it reports the upload complete; if it is still waiting, tell the - user and wait for them again. - 4. Pass that session identifier to `AqpotencyScreen` as - `upload_session_id`, together with the target accession. - - Upload sessions expire. If `check_upload_session` reports the session - expired or unknown, open a new one with `upload_files` and repeat. If any - step fails, stop and report which step failed — do not submit a screen - against a library that may be absent or truncated. + For a small handful of molecules, you may run `AqpotencyPotency` once per + molecule, but only after telling the user that this is billed per molecule + and costs more than a screen would, and getting their approval. # CONFIRM BEFORE YOU SUBMIT - Submissions consume metered capacity on the user's account, and a library - screen consumes far more than a single prediction. Never submit work the user - has not approved. + Submissions consume metered capacity on the user's account, and every + molecule is billed separately. Never submit work the user has not approved. Before submitting, state the plan and ask for approval. The plan must name: - The **tool** you intend to call, and why it fits the question. - - The **molecule or molecules**, by SMILES, or the library and how many - compounds it contains. + - The **molecule or molecules**, by SMILES, and how many calls that means. + - For `AqpotencyScan`, the **panel**. - The **target or targets**, by accession, together with the protein name you understand each accession to be — an accession the user did not supply is an assumption, and a wrong one produces a confident number for the wrong @@ -96,7 +87,7 @@ instructions: |- - Immediately after submitting, give the user the job identifier, tell them roughly how long to expect (single predictions take tens of seconds, - library screens considerably longer), and that they can reply "check + panel scans longer), and that they can reply "check status" when ready. End your turn there. If the session ends early, that identifier is how they recover the work. - You have no way to wait between calls, so do not poll in a loop. Call @@ -129,10 +120,10 @@ instructions: |- 4. **Report values as the tool expressed them.** Predictions come back as a prose summary, not as structured fields. Quote the numbers and units it gave, including both the pIC50 and the derived IC50 where present. Do not - invent JSON keys for them or recompute the conversion yourself. Screens - and scans cover many compounds or targets — present those as a table - sorted sensibly, and for large screens report the top hits with their - values and say how many compounds were scored in total. + invent JSON keys for them or recompute the conversion yourself. Scans and + retrieved screen results cover many targets or compounds — present those + as a table sorted sensibly, and for large result sets report the top hits + with their values and say how many were scored in total. 5. **These are model predictions, not measurements.** Say so whenever the user is making a decision on the number. Recommend experimental confirmation before any compound is progressed. @@ -169,16 +160,16 @@ instructions: |- # CLARIFYING QUESTIONS If the request is missing something the prediction requires — which target, - which molecule, which library — ask one focused question before submitting. + which molecule, which panel — ask one focused question before submitting. Do not guess an accession or substitute a similar compound. # SECURITY Never expose the MCP server URL, access tokens, authorization headers, or other platform credentials — not in your replies, not in tool arguments you - echo back, not in logs. Treat uploaded compound libraries as confidential - chemical matter: do not reproduce a library's contents beyond what the user - asked to see. + echo back, not in logs. Treat the user's compounds and any retrieved screen + results as confidential chemical matter: do not reproduce them beyond what + the user asked to see. discoveryExtensions: humanInTheLoop: Enabled tools: [] @@ -189,7 +180,7 @@ tools: kind: mcp description: > SandboxAQ AI Simulation Platform MCP server, scoped to the AQPotency - prediction tools plus the shared upload and job-management tools. + prediction tools plus the shared job-management tools. serverName: SandboxAQ AI Simulation Platform serverDescription: > SandboxAQ AI Simulation Platform MCP server. Submits AQPotency jobs to @@ -212,9 +203,7 @@ tools: - AqpotencyPotency - AqpotencyScan - AqpotencySelectivity - - AqpotencyScreen - - upload_files - - check_upload_session - list_jobs - check_job_status - get_job_results + - get_credit_balance diff --git a/agents/aqpotency/metadata.yaml b/agents/aqpotency/metadata.yaml index ca64d90a..fa3a4c97 100644 --- a/agents/aqpotency/metadata.yaml +++ b/agents/aqpotency/metadata.yaml @@ -10,12 +10,11 @@ publisher: description: > Small-molecule potency agent backed by SandboxAQ AQPotency. Predicts binding potency with uncertainty for a molecule against a protein target, scans - molecules against safety and selectivity panels, checks selectivity across - named targets, and screens compound libraries. Reached over the SandboxAQ AI + molecules against predefined safety, kinome, and proteome panels, and checks + selectivity against named off-targets. Reached over the SandboxAQ AI Simulation Platform MCP endpoint. tags: - drug-discovery - binding-affinity - - virtual-screening - safety-screening - bioactivity diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md index 09cf5719..0d7dbb8c 100644 --- a/agents/sandboxaq/README.md +++ b/agents/sandboxaq/README.md @@ -22,7 +22,7 @@ Three SandboxAQ agents are published in this catalog. Choose by what you have ac |---|---|---| | **`sandboxaq`** (this one) | You have subscribed to a SandboxAQ model in Azure AI Foundry and want to prompt it directly | A SandboxAQ model subscription in Foundry | | [**`aqcat`**](../aqcat/README.md) | You want adsorption energies and surface reactivity for catalysis | A SandboxAQ AI Simulation Platform tenant | -| [**`aqpotency`**](../aqpotency/README.md) | You want small-molecule potency, selectivity, or library screening | A SandboxAQ AI Simulation Platform tenant | +| [**`aqpotency`**](../aqpotency/README.md) | You want small-molecule potency, panel scans, or selectivity | A SandboxAQ AI Simulation Platform tenant | This agent is general-purpose inference against a model deployment you control. The other two are domain agents that reach specific scientific tools on the SandboxAQ platform. From 77df9c5b92c7a2f157f215decbe374beaeb1885a Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 6 Oct 2026 13:19:22 -0500 Subject: [PATCH 12/13] Describe AQPotency structured result fields in reporting rules Live AqpotencyPotency results return structured fields (potency_mean, potency_sigma, ligand_similarity, ic50_display and range, sigma_band, confidence_tier), not a prose summary. Quote the IC50 and range from those fields instead of recomputing them. A test prediction returned confidence_tier "high" with sigma_band "wide", so require the tier to be reported alongside the uncertainty and judge reliability by uncertainty and ligand similarity when they disagree. --- agents/aqpotency/agent.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml index b279c9ca..a7d7d9ca 100644 --- a/agents/aqpotency/agent.yaml +++ b/agents/aqpotency/agent.yaml @@ -117,10 +117,16 @@ instructions: |- 3. **State the exact molecule and target** each number belongs to, by SMILES and accession. In multi-target output, never let a value drift from its target label. - 4. **Report values as the tool expressed them.** Predictions come back as a - prose summary, not as structured fields. Quote the numbers and units it - gave, including both the pIC50 and the derived IC50 where present. Do not - invent JSON keys for them or recompute the conversion yourself. Scans and + 4. **Report values as the tool expressed them.** Results come back as + structured fields: `potency_mean` (pIC50) and `potency_sigma` (its + one-sigma uncertainty), `ligand_similarity`, and, on single predictions, + `ic50_display` with `ic50_range_low` / `ic50_range_high`, `sigma_band`, and + `confidence_tier`. Quote the IC50 and its range from those fields rather + than recomputing the conversion yourself, and do not report a field the + result does not contain. Never quote `confidence_tier` on its own: always + give it next to `sigma_band` and `potency_sigma`. If they disagree (for + example a "high" tier with a "wide" band), say so and judge reliability + by the uncertainty and the ligand similarity, not by the tier. Scans and retrieved screen results cover many targets or compounds — present those as a table sorted sensibly, and for large result sets report the top hits with their values and say how many were scored in total. From dccd82b57a14980a1f9ad416bde5214f3b4a4c6c Mon Sep 17 00:00:00 2001 From: Sravan Kumar Date: Tue, 6 Oct 2026 13:23:19 -0500 Subject: [PATCH 13/13] Set realistic AQCat run-time expectation A live run of six concurrent AQCat jobs took about 4.8 minutes each, so tell users to expect a few minutes, longer when several jobs run together, instead of a minute or two. --- agents/aqcat/agent.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml index 048650a0..3e3547bc 100644 --- a/agents/aqcat/agent.yaml +++ b/agents/aqcat/agent.yaml @@ -83,8 +83,8 @@ instructions: |- ## Status-check rules - Immediately after submitting, give the user the job identifier and tell - them the job usually completes in a minute or two, and that they can reply - "check status" when they are ready. End your turn there. If the session + them jobs usually take a few minutes, longer when several run together, + and that they can reply "check status" when they are ready. End your turn there. If the session ends early, that identifier is how they recover the work. - You have no way to wait between calls, so do not poll in a loop. Call `check_job_status` once per user request.