Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
428ce21
fix(orka): harden turn cancellation lifecycle
sozercan Jul 10, 2026
cab91b8
fix(foundry): make continuation state durable
sozercan Jul 10, 2026
6e36c67
fix(maf): preserve session identity under load
sozercan Jul 10, 2026
cdb145e
fix(build): reuse remote context for instructions
sozercan Jul 10, 2026
35bb1dd
fix(brokered): honor Orka tool classes
sozercan Jul 10, 2026
f849858
fix(abi): preserve YAML edge-case scalars
sozercan Jul 10, 2026
a9ff498
fix(runtimes): make adapter cleanup cancellation-safe
sozercan Jul 10, 2026
a13ee14
fix(foundry): gate brokered model work by capacity
sozercan Jul 10, 2026
20defb9
fix(labels): protect generated image identity
sozercan Jul 10, 2026
11f5acc
fix(build): reject runtime target mismatches
sozercan Jul 10, 2026
566ae99
fix(api): normalize invalid request errors
sozercan Jul 10, 2026
78d1cdb
fix(foundry): bound brokered output state
sozercan Jul 11, 2026
595867f
fix(build): follow local symlinks on target platform
sozercan Jul 11, 2026
3dcb9bb
fix(abi): normalize all YAML string scalars
sozercan Jul 11, 2026
c07eeec
fix(orka): validate external runtime endpoints
sozercan Jul 11, 2026
734692a
fix(pydantic-ai): bound stdio MCP calls
sozercan Jul 11, 2026
ea461a0
fix(maf): own adapter-created resources
sozercan Jul 11, 2026
6255e30
fix(foundry): bound hosted request and model responses
sozercan Jul 11, 2026
519499d
fix(image): preserve full target platform
sozercan Jul 11, 2026
a7ab711
fix(config): allow benign brokered descriptions
sozercan Jul 11, 2026
f4e53a6
fix(orka): align turn paths and tool outputs
sozercan Jul 11, 2026
dbdb089
fix(maf): bound MCP skills initialization
sozercan Jul 11, 2026
7b2ed4e
fix(dev): expose authenticated test agent
sozercan Jul 11, 2026
aa15d76
fix(foundry): prefer hosted invocation sessions
sozercan Jul 11, 2026
d17b054
fix(conformance): propagate hosted continuation fields
sozercan Jul 11, 2026
b4c805b
fix(config): align brokered description validation
sozercan Jul 11, 2026
7d664f1
fix(orka): bound turn output frames
sozercan Jul 11, 2026
6dc16d7
chore(go): mark fsutil as a direct test dependency
sozercan Jul 11, 2026
2b599d5
fix(orka): drain evicted runtime cleanup
sozercan Jul 11, 2026
7928782
chore: merge main into reliability
sozercan Jul 14, 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
34 changes: 26 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ ifneq ($(strip $(BUILDER)),)
BUILDER_FLAG := --builder $(BUILDER)
endif

# The runtime adapter image (agentkit-serve) is linux/amd64 (its uv base is
# amd64-only), so the test agent is built for the same platform.
# PLATFORM keeps the runtime adapter, generated agent image, and local run on
# one target architecture. linux/amd64 remains the compatibility default;
# override it (for example, PLATFORM=linux/arm64) for native ARM builds.
PLATFORM ?= linux/amd64

# The local run target binds the service across the container namespace, which
# requires bearer auth. Override this non-production token when desired, e.g.
# `make run-test-agent LOCAL_AUTH_TOKEN=my-local-token`.
LOCAL_AUTH_TOKEN ?= agentkit-local-dev-token

# RUNTIME selects which runtime adapter the test-agent targets: `pydantic-ai`
# (default), Microsoft Agent Framework (`maf` alias or canonical name), or
# LangGraph (`langgraph`). build-test-agent derives the adapter image, fixture,
Expand Down Expand Up @@ -88,20 +94,23 @@ build-agentkit:
# keeps the context small).
.PHONY: build-serve
build-serve:
docker buildx build . -f runtimes/pydantic-ai/Dockerfile -t agentkit-serve:$(TAG) --load
docker buildx build . -f runtimes/pydantic-ai/Dockerfile \
--platform $(PLATFORM) -t agentkit-serve:$(TAG) --load

# Build the Microsoft Agent Framework runtime adapter (agentkit-serve-maf) image.
# This is the LLB base used when an agentkitfile selects
# `runtime: microsoft-agent-framework` (alias `maf`).
.PHONY: build-serve-maf
build-serve-maf:
docker buildx build . -f runtimes/microsoft-agent-framework/Dockerfile -t agentkit-serve-maf:$(TAG) --load
docker buildx build . -f runtimes/microsoft-agent-framework/Dockerfile \
--platform $(PLATFORM) -t agentkit-serve-maf:$(TAG) --load

# Build the LangGraph runtime adapter (agentkit-serve-langgraph) image.
# This is the LLB base used when an agentkitfile selects `runtime: langgraph`.
.PHONY: build-serve-langgraph
build-serve-langgraph:
docker buildx build . -f runtimes/langgraph/Dockerfile -t agentkit-serve-langgraph:$(TAG) --load
docker buildx build . -f runtimes/langgraph/Dockerfile \
--platform $(PLATFORM) -t agentkit-serve-langgraph:$(TAG) --load

# Build a test agent against the LOCAL frontend (BUILDKIT_SYNTAX) and the LOCAL
# adapter (--build-arg adapter). The runtime, fixture, adapter image, and output
Expand All @@ -116,8 +125,17 @@ build-test-agent:
--platform $(PLATFORM) \
-t $(AGENT_IMAGE) --load --provenance=false

# Run the built test agent. Expects OPENAI_API_KEY in the environment; the agent
# binds 127.0.0.1 inside the container and serves the OpenAI /v1 façade on :8080.
# Run the built test agent. Expects OPENAI_API_KEY in the environment and forwards
# it by name so its value never appears in the command line. The service must bind
# 0.0.0.0 inside the container to cross the container network namespace, while the
# published host socket remains loopback-only. The expanded curl command records
# the configurable local bearer token required by that non-loopback container bind.
.PHONY: run-test-agent
run-test-agent:
docker run --rm --platform $(PLATFORM) -p 127.0.0.1:8080:8080 -e OPENAI_API_KEY=$$OPENAI_API_KEY $(AGENT_IMAGE)
@printf '%s\n' 'curl -fsS -H "Authorization: Bearer $(LOCAL_AUTH_TOKEN)" http://127.0.0.1:8080/v1/models'
docker run --rm --platform $(PLATFORM) \
-p 127.0.0.1:8080:8080 \
-e AGENTKIT_BIND=0.0.0.0 \
-e AGENTKIT_AUTH_TOKEN="$(LOCAL_AUTH_TOKEN)" \
-e OPENAI_API_KEY \
$(AGENT_IMAGE)
130 changes: 112 additions & 18 deletions deploy/foundry/scripts/foundry_brokered_conformance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Optional:
AGENTKIT_EXPECTED_CALL_ID_PREFIX Optional required call_id prefix, e.g. call_.
AGENTKIT_EXPECTED_FINAL_TEXT Optional exact final assistant text; defaults are derived for SDK/AgentKit fixtures.
AGENTKIT_CONTINUATION_PROOF Optional x-agentkit-brokered-continuation-proof header.
AGENTKIT_CONTINUATION_PROOF_BODY Optional proof sent only in the live continuation body.
It is omitted from the archived request transcript.
EOF
}

Expand Down Expand Up @@ -100,7 +102,15 @@ continuation_response="$transcript_dir/04-continuation-response.json"
summary_file="$transcript_dir/summary.json"
expected_output_file="$transcript_dir/.expected-output.json"
expected_final_text_file="$transcript_dir/.expected-final-text.txt"
trap 'rm -f -- "$expected_output_file" "$expected_final_text_file"' EXIT
rm -f "$continuation_response" "$summary_file" "$summary_file.tmp" "$expected_output_file" "$expected_final_text_file"
continuation_response_wire="$(mktemp "${TMPDIR:-/tmp}/agentkit-foundry-continuation-response.XXXXXX")"
continuation_request_wire="$(mktemp "${TMPDIR:-/tmp}/agentkit-foundry-continuation-request.XXXXXX")"

cleanup() {
rm -f "$continuation_response_wire" "$continuation_request_wire" "$summary_file.tmp" \
"$expected_output_file" "$expected_final_text_file"
}
trap cleanup EXIT
printf '%s' "$conformance_output" >"$expected_output_file"
printf '%s' "$expected_final_text" >"$expected_final_text_file"

Expand All @@ -118,7 +128,12 @@ printf '%s\n' "$initial_curl_config" | curl -fsS \
-d "@$initial_request" >"$initial_response"
unset initial_curl_config

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'
EXPECTED_TOOL_NAME="$expected_tool_name" \
EXPECTED_ARGUMENTS="$expected_arguments" \
EXPECTED_CALL_ID="$expected_call_id" \
EXPECTED_CALL_ID_PREFIX="$expected_call_id_prefix" \
CONFORMANCE_OUTPUT="$conformance_output" \
python3 - "$initial_response" <<'PY' >"$continuation_request"
import json
import os
import sys
Expand All @@ -145,26 +160,40 @@ if expected_call_id != "auto":
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"],
conformance_output = os.environ["CONFORMANCE_OUTPUT"]
json.loads(conformance_output)
continuation = {
"previous_response_id": response_id,
"input": [{
"type": "function_call_output",
"call_id": os.environ["CALL_ID"],
"output": os.environ["CONFORMANCE_OUTPUT"],
"call_id": call_id,
"output": conformance_output,
"status": "completed",
}],
}, separators=(",", ":")))
}
agent_session_id = body.get("agent_session_id")
if agent_session_id is not None:
assert isinstance(agent_session_id, str) and agent_session_id.strip(), body
continuation["agent_session_id"] = agent_session_id
print(json.dumps(continuation, separators=(",", ":")))
PY

cp "$continuation_request" "$continuation_request_wire"
if [[ -n "${AGENTKIT_CONTINUATION_PROOF_BODY:-}" ]]; then
AGENTKIT_CONTINUATION_PROOF_BODY="$AGENTKIT_CONTINUATION_PROOF_BODY" \
python3 -c '
import json
import os
import sys
from pathlib import Path

body = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
body["brokered_continuation_proof"] = os.environ["AGENTKIT_CONTINUATION_PROOF_BODY"]
print(json.dumps(body, separators=(",", ":")))
' "$continuation_request" >"$continuation_request_wire"
fi

continuation_curl_config="$({
write_curl_header "Authorization: Bearer ${token}"
if [[ -n "${AGENTKIT_CONTINUATION_PROOF:-}" ]]; then
Expand All @@ -175,9 +204,75 @@ printf '%s\n' "$continuation_curl_config" | curl -fsS \
--config - \
-H 'content-type: application/json' \
"$AGENT_RESPONSES_ENDPOINT" \
-d "@$continuation_request" >"$continuation_response"
-d "@$continuation_request_wire" >"$continuation_response_wire"
unset continuation_curl_config

AGENTKIT_CONTINUATION_PROOF="${AGENTKIT_CONTINUATION_PROOF:-}" \
AGENTKIT_CONTINUATION_PROOF_BODY="${AGENTKIT_CONTINUATION_PROOF_BODY:-}" \
python3 - "$continuation_response_wire" <<'PY'
import json
import os
import re
import sys
from pathlib import Path

_JSON_ESCAPE_RE = re.compile(r'(?:\\u[0-9A-Fa-f]{4}){1,2}|\\["\\/bfnrt]')

def decoded_fragment_contains_proof(value, proof):
seen = set()
while value not in seen:
seen.add(value)
if proof in value:
return True

def decode_escape(match):
try:
return json.loads(f'"{match.group(0)}"')
except (ValueError, RecursionError):
return match.group(0)

decoded = _JSON_ESCAPE_RE.sub(decode_escape, value)
if decoded == value:
return False
value = decoded
return False


def contains_proof(value, proof):
pending = [value]
decoded_strings = set()
while pending:
current = pending.pop()
if isinstance(current, str):
if decoded_fragment_contains_proof(current, proof):
return True
if current in decoded_strings:
continue
decoded_strings.add(current)
try:
pending.append(json.loads(current))
except (ValueError, RecursionError):
pass
elif isinstance(current, list):
pending.extend(current)
elif isinstance(current, dict):
pending.extend(current.keys())
pending.extend(current.values())
return False


raw = Path(sys.argv[1]).read_text(encoding="utf-8")
try:
decoded = json.loads(raw)
except json.JSONDecodeError:
decoded = None
for name in ("AGENTKIT_CONTINUATION_PROOF", "AGENTKIT_CONTINUATION_PROOF_BODY"):
proof = os.environ.get(name, "")
if proof and (contains_proof(raw, proof) or contains_proof(decoded, proof)):
raise SystemExit("gateway response contains continuation proof; refusing to archive transcript")
PY
mv "$continuation_response_wire" "$continuation_response"

verifier_args=(
"$transcript_dir"
--expected-tool-name "$expected_tool_name"
Expand All @@ -191,8 +286,7 @@ 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"
rm -f "$expected_output_file" "$expected_final_text_file"
cleanup
trap - EXIT

echo "Foundry brokered conformance passed. Sanitized transcript: ${transcript_dir}"
Expand Down
24 changes: 21 additions & 3 deletions deploy/foundry/scripts/verify_brokered_transcript.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ def verify_transcript(
expected_call_id: str = "call_conformance_1",
expected_call_id_prefix: str | None = None,
) -> dict[str, Any]:
_require(expected_final_text is not None, "expected final text is required for transcript verification")
root = Path(transcript_dir)
expected_arguments = _parse_json_lossless(expected_arguments_json)
expected_output = _parse_json_lossless(expected_output_json)
Expand All @@ -132,6 +131,10 @@ def verify_transcript(
continuation_response = _load_json(root / "04-continuation-response.json")

_require(isinstance(initial_request, dict), "initial request must be a JSON object")
_require(
"brokered_continuation_proof" not in initial_request,
"sanitized initial request must not archive a continuation proof",
)
_require("tools" not in initial_request, "initial request must not contain request-level tools")
_require("input" in initial_request, "initial request must contain input")

Expand All @@ -140,6 +143,12 @@ def verify_transcript(
initial_response_id = initial_response.get("id")
_require(isinstance(initial_response_id, str) and initial_response_id.startswith("caresp_"), "initial response id must start with caresp_")
_require(not initial_response_id.startswith("resp_"), "initial response id must not use old resp_ format")
agent_session_id = initial_response.get("agent_session_id")
if agent_session_id is not None:
_require(
isinstance(agent_session_id, str) and bool(agent_session_id.strip()),
"initial response agent_session_id must be a non-empty string",
)
output = initial_response.get("output")
_require(isinstance(output, list) and len(output) == 1, "initial response output must contain exactly one item")
call = output[0]
Expand All @@ -161,7 +170,16 @@ def verify_transcript(
_require(_strict_json_equal(parsed_arguments, expected_arguments), f"function_call arguments must be {expected_arguments}")

_require(isinstance(continuation_request, dict), "continuation request must be a JSON object")
_require(
"brokered_continuation_proof" not in continuation_request,
"sanitized continuation request must not archive a continuation proof",
)
_require(continuation_request.get("previous_response_id") == initial_response_id, "continuation previous_response_id must match initial id")
if agent_session_id is not None:
_require(
continuation_request.get("agent_session_id") == agent_session_id,
"continuation agent_session_id must match the initial response",
)
continuation_input = continuation_request.get("input")
_require(isinstance(continuation_input, list) and len(continuation_input) == 1, "continuation input must contain exactly one item")
continuation_item = continuation_input[0]
Expand All @@ -181,8 +199,8 @@ def verify_transcript(
_require(isinstance(continuation_response_id, str) and continuation_response_id.startswith("caresp_"), "continuation response id must start with caresp_")
_require(continuation_response_id != initial_response_id, "continuation response id must differ from initial response id")
final_text = _message_text(continuation_response, response_id=continuation_response_id)
if expected_final_text is not None:
_require(final_text == expected_final_text, "final message text did not match expected conformance result")
_require(expected_final_text is not None, "expected final text is required for transcript verification")
_require(final_text == expected_final_text, "final message text did not match expected conformance result")

return {
"initial_response_id": initial_response_id,
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ model:
provider: openai-compatible
baseURL: https://api.openai.com/v1
name: gpt-4o-mini
apiKeyEnv: OPENAI_API_KEY
apiKeyEnv: "OPENAI_API_KEY"
# Optional future generic model auth. Capability-gated; apiKeyEnv remains the
# normal v0 model-auth path.
# auth:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.4
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f
golang.org/x/sync v0.20.0
google.golang.org/grpc v1.81.1
)
Expand Down Expand Up @@ -43,7 +44,6 @@ require (
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect
Expand Down
Loading
Loading