Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7b9f0f6
feat(foundry): add brokered responses mode
sozercan Jul 9, 2026
268a67c
fix(foundry): harden brokered response auth
sozercan Jul 9, 2026
bd9641c
fix(config): align brokered auth text validation
sozercan Jul 9, 2026
ba92655
fix(foundry): reject lossy model-loop floats
sozercan Jul 9, 2026
4ad34a9
fix(foundry): tidy doctor auth warning
sozercan Jul 9, 2026
9a71858
fix(foundry): address brokered review feedback
sozercan Jul 12, 2026
82a98ae
fix(foundry): address follow-up review feedback
sozercan Jul 12, 2026
528e8ff
fix(foundry): bound brokered continuation outputs
sozercan Jul 12, 2026
021c83d
fix(foundry): reject lossy tool output numbers
sozercan Jul 12, 2026
6e61abb
fix(brokered): harden validation and fixtures
sozercan Jul 12, 2026
5ce2d5b
fix(brokered): preserve schema and transcript fidelity
sozercan Jul 12, 2026
6c0234b
fix(config): preserve brokered schema numbers
sozercan Jul 12, 2026
14cad45
fix(brokered): normalize usage and schema edge cases
sozercan Jul 12, 2026
a0a9729
fix(brokered): preserve numeric ABI fidelity
sozercan Jul 12, 2026
67e69b0
fix(foundry): reject decoded model arguments
sozercan Jul 12, 2026
10ae43a
fix(config): preserve negative zero schema values
sozercan Jul 12, 2026
85ffae8
fix(foundry): harden fallback and fixtures
sozercan Jul 12, 2026
7400cd2
fix(brokered): harden synthesis and deep copies
sozercan Jul 12, 2026
e6d3b25
fix(brokered): close conformance and continuation gaps
sozercan Jul 12, 2026
75957e6
fix(foundry): harden persisted continuation state
sozercan Jul 12, 2026
0b33dc2
fix(conformance): harden transcript evidence
sozercan Jul 12, 2026
ba81cd7
fix(foundry): distinguish expired continuation states
sozercan Jul 12, 2026
42006c6
fix(foundry): reject ambiguous broker output
sozercan Jul 12, 2026
e1b3db1
fix(conformance): validate complete broker exchange
sozercan Jul 12, 2026
f0a3d72
fix(foundry): close brokered validation gaps
sozercan Jul 12, 2026
10addc8
fix(foundry): harden brokered response validation
sozercan Jul 13, 2026
52309dd
fix(foundry): bound brokered state and transcript data
sozercan Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ jobs:
deploy/foundry/search/setup.sh \
deploy/foundry/memory/setup.sh \
deploy/foundry/rbac/assign-agent-identity.sh \
deploy/foundry/scripts/invoke_responses.sh; do
deploy/foundry/scripts/invoke_responses.sh \
deploy/foundry/scripts/foundry_brokered_conformance.sh \
deploy/foundry/scripts/local_brokered_conformance_container.sh; do
bash -n "$script"
done
python3 -m py_compile test/foundry-hosted-agent/foundry_live.py
python3 -m py_compile test/foundry-hosted-agent/foundry_live.py deploy/foundry/scripts/verify_brokered_transcript.py
python3 - <<'PY'
from pathlib import Path
import re
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ __pycache__/

# Local provider validation outputs
deploy/foundry/**/output.env

# Local Foundry brokered conformance transcripts (request/response evidence may contain endpoint-specific data).
foundry-brokered-transcript*/
foundry-brokered-local-transcript*/
agentkit-foundry-brokered.*/
agentkit-foundry-brokered-local.*/
46 changes: 46 additions & 0 deletions deploy/foundry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,53 @@ as a Foundry-hosted protocol image.
the project/account role required for workload-identity model/tool calls (defaults to `Foundry User`).
- `scripts/invoke_responses.sh` sends the minimal portable hosted Responses
payload (`{"input":"..."}`), avoiding gateway-specific optional fields.
- `scripts/foundry_brokered_conformance.sh` runs the Phase A0 brokered
Responses function-call/continuation loop against a deployed `/responses`
endpoint and writes a sanitized transcript directory for review evidence.
- `scripts/local_brokered_conformance_container.sh` builds the conformance
container locally, runs it, and exercises the same transcript helper before an
image is pushed to Foundry.
- `doctor.sh` checks the expected Foundry/project env and local CLI prerequisites.
Use `doctor.sh --brokered-conformance` before running the brokered transcript
helper to verify `AGENT_RESPONSES_ENDPOINT` and auth prerequisites.

These scripts intentionally produce local `output.env` files that should not be
committed with live subscription or endpoint values.


## Brokered conformance smoke


Before pushing the image, validate the packaged container locally:

```sh
deploy/foundry/scripts/local_brokered_conformance_container.sh \
--platform linux/amd64 \
--tag agentkit-foundry-brokered-conformance:amd64-test \
--port 18090 \
--transcript-dir ./foundry-brokered-local-transcript
```

After deploying an image that serves
`agentkit_serve_common.foundry_conformance.create_foundry_conformance_app()`, run:

```sh
export AGENT_RESPONSES_ENDPOINT="https://<hosted-agent>/responses"
# Optional: export AZURE_SUBSCRIPTION_ID="<subscription>" to select an account.
deploy/foundry/doctor.sh --brokered-conformance
deploy/foundry/scripts/foundry_brokered_conformance.sh conformance_read ./foundry-brokered-transcript
```

To validate the production AgentKit brokered path locally instead of the
standalone SDK conformance app, see `test/foundry-brokered-agentkit/`. That
fixture uses `agentkit-foundry-brokered` and expects generated call IDs, so run
the transcript helper with `AGENTKIT_EXPECTED_CALL_ID=auto` and
`AGENTKIT_EXPECTED_CALL_ID_PREFIX=call_`.

Alternatively set `AGENT_RESPONSES_BEARER_TOKEN` to use a pre-acquired token
instead of invoking `az account get-access-token`. If `AZURE_SUBSCRIPTION_ID` is
omitted, the helper uses the current `az` account. The script stores request and
response JSON files plus `summary.json`; do not include bearer tokens in the
transcript. Re-run
`python3 deploy/foundry/scripts/verify_brokered_transcript.py <transcript-dir>`
to verify archived transcript evidence later.
68 changes: 62 additions & 6 deletions deploy/foundry/doctor.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail

usage() {
cat >&2 <<'EOF'
usage: doctor.sh [--brokered-conformance]

Default mode checks the generic Foundry deployment helper prerequisites.
--brokered-conformance checks the env/tools needed to run
deploy/foundry/scripts/foundry_brokered_conformance.sh
against a deployed hosted-agent /responses endpoint.
EOF
}

mode="default"
case "${1:-}" in
"") ;;
--brokered-conformance) mode="brokered-conformance" ;;
-h|--help) usage; exit 0 ;;
*) usage; exit 2 ;;
esac

missing=0
need() {
if [[ -z "${!1:-}" ]]; then
printf 'missing env: %s\n' "$1" >&2
missing=1
fi
}
need FOUNDRY_PROJECT_ENDPOINT
if ! command -v az >/dev/null 2>&1; then
printf 'warning: az CLI not found; hosted resource/RBAC checks cannot run locally\n' >&2
fi
if ! command -v azd >/dev/null 2>&1; then
printf 'warning: azd CLI not found; hosted-agent deploy/invoke checks cannot run locally\n' >&2
need_command() {
if ! command -v "$1" >/dev/null 2>&1; then
printf '%s: %s\n' "$2" "$1" >&2
if [[ "${3:-required}" == "required" ]]; then
missing=1
fi
fi
}

if [[ "$mode" == "brokered-conformance" ]]; then
need AGENT_RESPONSES_ENDPOINT
need_command curl "missing command"
need_command python3 "missing command"
if [[ -z "${AGENT_RESPONSES_BEARER_TOKEN:-}" ]]; then
need_command az "missing command"
if command -v az >/dev/null 2>&1; then
if [[ -n "${AZURE_SUBSCRIPTION_ID:-}" ]]; then
az account set --subscription "$AZURE_SUBSCRIPTION_ID" >/dev/null 2>&1 || missing=1
fi
if ! az account show >/dev/null 2>&1; then
printf 'missing auth: set AGENT_RESPONSES_BEARER_TOKEN or run az login/select an account\n' >&2
missing=1
fi
fi
fi
if [[ ! -x deploy/foundry/scripts/foundry_brokered_conformance.sh ]]; then
printf 'missing executable: deploy/foundry/scripts/foundry_brokered_conformance.sh\n' >&2
missing=1
fi
if [[ ! -f deploy/foundry/scripts/verify_brokered_transcript.py ]]; then
printf 'missing verifier: deploy/foundry/scripts/verify_brokered_transcript.py\n' >&2
missing=1
fi
if [[ "$missing" -ne 0 ]]; then
exit 2
fi
printf 'foundry doctor: brokered conformance prerequisites checked\n'
exit 0
fi

need FOUNDRY_PROJECT_ENDPOINT
need_command az "warning: az CLI not found; hosted resource/RBAC checks cannot run locally" optional
need_command azd "warning: azd CLI not found; hosted-agent deploy/invoke checks cannot run locally" optional
if [[ "$missing" -ne 0 ]]; then
exit 2
fi
Expand Down
149 changes: 149 additions & 0 deletions deploy/foundry/scripts/foundry_brokered_conformance.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env bash
set -euo pipefail
Comment thread
Copilot marked this conversation as resolved.

usage() {
cat >&2 <<'EOF'
usage: foundry_brokered_conformance.sh [prompt] [transcript-dir]

Runs the Phase A0 hosted Responses brokered conformance loop against a deployed
Foundry hosted agent endpoint:
1. POST an initial /responses request with no request-level tools.
2. Assert the response contains the deterministic conformance_read function_call.
3. POST a function_call_output continuation with previous_response_id.
4. Assert the final response is a completed assistant message.

Required environment:
AGENT_RESPONSES_ENDPOINT Full deployed /responses endpoint URL.

Authentication, one of:
AGENT_RESPONSES_BEARER_TOKEN Pre-acquired bearer token for the endpoint.
AZURE_SUBSCRIPTION_ID Optional subscription to select before `az account get-access-token`.
If omitted, the current `az` account is used.

Optional:
AGENTKIT_CONFORMANCE_OUTPUT Defaults to {"approved":true,"output":{"success":true}}.
AGENTKIT_EXPECTED_TOOL_NAME Defaults to conformance_read.
AGENTKIT_EXPECTED_ARGUMENTS Defaults to {"probe":true}.
AGENTKIT_EXPECTED_CALL_ID Defaults to call_conformance_1; set to auto for generated IDs.
AGENTKIT_EXPECTED_CALL_ID_PREFIX Optional required call_id prefix, e.g. call_.
AGENTKIT_CONTINUATION_PROOF Optional x-agentkit-brokered-continuation-proof header.
EOF
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi

: "${AGENT_RESPONSES_ENDPOINT:?set AGENT_RESPONSES_ENDPOINT to the deployed /responses URL}"

prompt="${1:-conformance_read}"
transcript_dir="${2:-$(mktemp -d "${TMPDIR:-/tmp}/agentkit-foundry-brokered.XXXXXX")}"
mkdir -p "$transcript_dir"

if [[ -n "${AGENT_RESPONSES_BEARER_TOKEN:-}" ]]; then
token="$AGENT_RESPONSES_BEARER_TOKEN"
else
if [[ -n "${AZURE_SUBSCRIPTION_ID:-}" ]]; then
az account set --subscription "$AZURE_SUBSCRIPTION_ID" >/dev/null
else
az account show >/dev/null
fi
token="$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)"
fi

conformance_output="${AGENTKIT_CONFORMANCE_OUTPUT:-{\"approved\":true,\"output\":{\"success\":true}}}"
expected_tool_name="${AGENTKIT_EXPECTED_TOOL_NAME:-conformance_read}"
expected_arguments="${AGENTKIT_EXPECTED_ARGUMENTS:-{\"probe\":true}}"
expected_call_id="${AGENTKIT_EXPECTED_CALL_ID:-call_conformance_1}"
expected_call_id_prefix="${AGENTKIT_EXPECTED_CALL_ID_PREFIX:-}"
initial_request="$transcript_dir/01-initial-request.json"
initial_response="$transcript_dir/02-initial-response.json"
continuation_request="$transcript_dir/03-continuation-request.json"
continuation_response="$transcript_dir/04-continuation-response.json"
summary_file="$transcript_dir/summary.json"

PROMPT="$prompt" python3 - <<'PY' >"$initial_request"
import json
import os
print(json.dumps({"input": os.environ["PROMPT"]}, separators=(",", ":")))
PY

curl -fsS \
-H "Authorization: Bearer ${token}" \
-H 'content-type: application/json' \
"$AGENT_RESPONSES_ENDPOINT" \
-d "@$initial_request" >"$initial_response"
Comment thread
sozercan marked this conversation as resolved.
Outdated

read -r response_id call_id < <(EXPECTED_TOOL_NAME="$expected_tool_name" EXPECTED_ARGUMENTS="$expected_arguments" EXPECTED_CALL_ID="$expected_call_id" EXPECTED_CALL_ID_PREFIX="$expected_call_id_prefix" python3 - "$initial_response" <<'PY'
import json
import os
import sys
from pathlib import Path

body = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
assert body.get("status") == "completed", body
response_id = body.get("id")
assert isinstance(response_id, str) and response_id.startswith("caresp_"), body
assert not response_id.startswith("resp_"), body
output = body.get("output")
assert isinstance(output, list) and len(output) == 1, body
call = output[0]
assert call.get("type") == "function_call", call
expected_tool_name = os.environ["EXPECTED_TOOL_NAME"]
expected_arguments = json.loads(os.environ["EXPECTED_ARGUMENTS"])
expected_call_id = os.environ["EXPECTED_CALL_ID"]
expected_call_id_prefix = os.environ.get("EXPECTED_CALL_ID_PREFIX", "")
assert call.get("name") == expected_tool_name, call
call_id = call.get("call_id")
assert isinstance(call_id, str) and call_id, call
if expected_call_id != "auto":
assert call_id == expected_call_id, call
if expected_call_id_prefix:
assert call_id.startswith(expected_call_id_prefix), call
assert json.loads(call.get("arguments") or "{}") == expected_arguments, call
print(response_id, call_id)
PY
)

PREVIOUS_RESPONSE_ID="$response_id" CALL_ID="$call_id" CONFORMANCE_OUTPUT="$conformance_output" python3 - <<'PY' >"$continuation_request"
import json
import os
# Validate the configured output is JSON before placing it in the Responses item.
json.loads(os.environ["CONFORMANCE_OUTPUT"])
print(json.dumps({
"previous_response_id": os.environ["PREVIOUS_RESPONSE_ID"],
"input": [{
"type": "function_call_output",
"call_id": os.environ["CALL_ID"],
"output": os.environ["CONFORMANCE_OUTPUT"],
"status": "completed",
}],
}, separators=(",", ":")))
PY

continuation_headers=(-H "Authorization: Bearer ${token}" -H 'content-type: application/json')
if [[ -n "${AGENTKIT_CONTINUATION_PROOF:-}" ]]; then
continuation_headers+=(-H "x-agentkit-brokered-continuation-proof: ${AGENTKIT_CONTINUATION_PROOF}")
fi

curl -fsS \
"${continuation_headers[@]}" \
"$AGENT_RESPONSES_ENDPOINT" \
-d "@$continuation_request" >"$continuation_response"
Comment thread
sozercan marked this conversation as resolved.
Outdated

verifier_args=(
"$transcript_dir"
--expected-tool-name "$expected_tool_name"
--expected-arguments-json "$expected_arguments"
--expected-call-id "$expected_call_id"
--write-summary
)
if [[ -n "$expected_call_id_prefix" ]]; then
verifier_args+=(--expected-call-id-prefix "$expected_call_id_prefix")
fi
python3 deploy/foundry/scripts/verify_brokered_transcript.py "${verifier_args[@]}" >"$summary_file.tmp"
rm -f "$summary_file.tmp"

echo "Foundry brokered conformance passed. Sanitized transcript: ${transcript_dir}"
cat "$summary_file"
Loading
Loading