Skip to content

feat(slack-bot): add escalation workflows, fix feedback/streaming bugs, refactor overthink #953

feat(slack-bot): add escalation workflows, fix feedback/streaming bugs, refactor overthink

feat(slack-bot): add escalation workflows, fix feedback/streaming bugs, refactor overthink #953

name: "[CI][A2A] Sub-Agents A2A Docker Build and Push"
description: "Build and push A2A Docker images for sub-agents"
on:
# Trigger on push to main to detect changes and build/retag images
push:
branches:
- main
paths:
- 'ai_platform_engineering/**'
- 'build/**'
- 'pyproject.toml'
- 'uv.lock'
# Build on all manual new tags as well
tags:
- '**'
pull_request:
branches:
- main
paths:
- 'ai_platform_engineering/utils/**'
- 'ai_platform_engineering/agents/**'
- 'build/agents/Dockerfile.a2a'
workflow_dispatch:
inputs:
build_all:
description: 'Build all containers (skip change detection)'
required: false
default: false
type: boolean
tag_version:
description: 'Version tag to apply (e.g., 0.2.8-rc.1). If provided, unchanged agents will be retagged from previous version.'
required: false
type: string
jobs:
load-config:
runs-on: ubuntu-latest
if: always()
outputs:
all_agents: ${{ steps.read-config.outputs.all_agents }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: .github/agents.json
sparse-checkout-cone-mode: false
- name: Read agents config
id: read-config
run: |
ALL_AGENTS=$(jq -c '.a2a_agents' .github/agents.json)
echo "all_agents=$ALL_AGENTS" >> $GITHUB_OUTPUT
determine-agents:
runs-on: ubuntu-latest
needs: load-config
if: |
((github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')))) ||
(github.event_name == 'pull_request' && !startsWith(github.head_ref, 'prebuild/')) ||
(github.event_name == 'workflow_dispatch')
outputs:
agents_to_build: ${{ steps.set-matrix.outputs.agents_to_build }}
agents_to_retag: ${{ steps.set-matrix.outputs.agents_to_retag }}
should_build: ${{ steps.set-matrix.outputs.should_build }}
should_retag: ${{ steps.set-matrix.outputs.should_retag }}
tag_version: ${{ steps.version.outputs.tag_version }}
env:
ALL_AGENTS: ${{ needs.load-config.outputs.all_agents }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Determine Tag
id: version
uses: ./.github/actions/determine-release-tag
with:
manual_tag: ${{ inputs.tag_version }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Generate path filters
id: generate-filters
uses: actions/github-script@v8
with:
script: |
const agents = JSON.parse(process.env.ALL_AGENTS);
let filters = 'shared_utils:\n';
filters += ' - \'ai_platform_engineering/utils/**\'\n';
filters += 'shared_config:\n';
filters += ' - \'pyproject.toml\'\n';
filters += ' - \'uv.lock\'\n';
filters += 'shared_dockerfile:\n';
filters += ' - \'build/agents/Dockerfile.a2a\'\n';
agents.forEach(agent => {
filters += `${agent}:\n`;
filters += ` - 'ai_platform_engineering/agents/${agent}/**'\n`;
filters += ` - '!ai_platform_engineering/agents/${agent}/mcp/**'\n`;
filters += ` - '!ai_platform_engineering/agents/${agent}/build/Dockerfile.mcp'\n`;
});
core.setOutput('filters', filters);
- name: Detect changed paths
id: filter
uses: dorny/paths-filter@v4
with:
predicate-quantifier: 'every'
filters: ${{ steps.generate-filters.outputs.filters }}
- name: Set matrix based on changes
id: set-matrix
uses: actions/github-script@v8
env:
FILTER_OUTPUTS: ${{ toJson(steps.filter.outputs) }}
TAG_VERSION: ${{ steps.version.outputs.tag_version }}
MANUAL_BUILD_ALL: ${{ inputs.build_all || 'false' }}
with:
script: |
const allAgents = JSON.parse(process.env.ALL_AGENTS);
const filterOutputs = JSON.parse(process.env.FILTER_OUTPUTS);
const tagVersion = process.env.TAG_VERSION;
const manualBuildAll = process.env.MANUAL_BUILD_ALL === 'true';
const isRc = /-rc\.\d+$/.test(tagVersion);
const isSharedChanged = filterOutputs.shared_utils === 'true' ||
filterOutputs.shared_config === 'true' ||
filterOutputs.shared_dockerfile === 'true';
// "Truly Build All" if:
// 1. Manual build_all input is true
// 2. Shared files (utils, config, dockerfile) changed
// 3. We have a tag, but it's NOT an RC tag (e.g. final release)
const shouldForceBuildAll = manualBuildAll || isSharedChanged || (tagVersion && !isRc);
let agentsToBuild;
let agentsToRetag = [];
if (shouldForceBuildAll) {
console.log("🚀 Forced 'Build All' mode activated");
agentsToBuild = allAgents;
agentsToRetag = [];
} else if (isRc) {
console.log(`🏷️ RC Tag detected (${tagVersion}): Using Build vs. Retag optimization`);
agentsToBuild = allAgents.filter(a => filterOutputs[a] === 'true');
agentsToRetag = allAgents.filter(a => filterOutputs[a] !== 'true');
} else {
console.log("🔍 Standard PR/Push: Building only changed agents");
agentsToBuild = allAgents.filter(a => filterOutputs[a] === 'true');
agentsToRetag = [];
}
core.setOutput('agents_to_build', JSON.stringify(agentsToBuild));
core.setOutput('agents_to_retag', JSON.stringify(agentsToRetag));
core.setOutput('should_build', String(agentsToBuild.length > 0));
core.setOutput('should_retag', String(agentsToRetag.length > 0 && !!tagVersion));
console.log(`Agents to build: ${agentsToBuild.join(', ') || 'none'}`);
console.log(`Agents to retag: ${agentsToRetag.join(', ') || 'none'}`);
build-and-push:
runs-on: ubuntu-latest
needs: [determine-agents, load-config]
if: (needs.determine-agents.outputs.should_build == 'true' && (github.event_name != 'pull_request' || !startsWith(github.head_ref, 'prebuild/')))
permissions:
contents: read
packages: write
strategy:
matrix:
agent: ${{ fromJson(needs.determine-agents.outputs.agents_to_build) }}
fail-fast: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/agent-${{ matrix.agent }}
AGENT_DIR: ai_platform_engineering/agents/${{ matrix.agent }}
steps:
- name: 🔒 harden runner
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }}
type=raw,value=${{ needs.determine-agents.outputs.tag_version }},enable=${{ needs.determine-agents.outputs.tag_version != '' }}
type=ref,event=branch,prefix=
type=ref,event=tag,prefix=
type=sha,format=short,prefix=
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Determine Dockerfile path
id: dockerfile
run: |
if [ -f "${{ env.AGENT_DIR }}/build/Dockerfile.a2a" ]; then
echo "path=${{ env.AGENT_DIR }}/build/Dockerfile.a2a" >> $GITHUB_OUTPUT
else
echo "path=build/agents/Dockerfile.a2a" >> $GITHUB_OUTPUT
fi
- name: Determine Agent Package Name
id: agent_package
run: |
# Special case: template agent uses agent_petstore package
if [ "${{ matrix.agent }}" == "template" ]; then
echo "name=petstore" >> $GITHUB_OUTPUT
else
echo "name=${{ matrix.agent }}" >> $GITHUB_OUTPUT
fi
- name: Build and Push A2A Docker image
uses: docker/build-push-action@v7
with:
context: .
file: ${{ steps.dockerfile.outputs.path }}
push: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
AGENT_NAME=${{ matrix.agent }}
AGENT_PACKAGE=${{ steps.agent_package.outputs.name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
# Retag unchanged agents from previous version when tag_version is provided
# If source image doesn't exist (still building), falls back to full build
retag-unchanged:
runs-on: ubuntu-latest
needs: determine-agents
if: needs.determine-agents.outputs.should_retag == 'true'
permissions:
contents: read
packages: write
strategy:
matrix:
agent: ${{ fromJson(needs.determine-agents.outputs.agents_to_retag) }}
fail-fast: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/agent-${{ matrix.agent }}
AGENT_DIR: ai_platform_engineering/agents/${{ matrix.agent }}
steps:
- name: 🔒 Harden runner
uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- name: 📦 Install crane
run: |
VERSION="v0.20.2"
curl -sL "https://github.com/google/go-containerregistry/releases/download/${VERSION}/go-containerregistry_Linux_x86_64.tar.gz" | tar xz crane
sudo mv crane /usr/local/bin/
- name: 🔐 Log in to registry
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GH_TOKEN" | crane auth login ghcr.io -u ${{ github.actor }} --password-stdin
- name: 🔍 Check source image and retag or build
id: retag-or-build
env:
TAG_VERSION: ${{ needs.determine-agents.outputs.tag_version }}
run: |
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}"
echo "🏷️ Processing agent-${{ matrix.agent }}..."
# Determine source tag (previous version)
if [[ "$TAG_VERSION" =~ ^(.+)-rc\.([0-9]+)$ ]]; then
BASE_VERSION="${BASH_REMATCH[1]}"
RC_NUM="${BASH_REMATCH[2]}"
if [[ "$RC_NUM" -gt 1 ]]; then
PREV_RC=$((RC_NUM - 1))
SOURCE_TAG="${BASE_VERSION}-rc.${PREV_RC}"
else
SOURCE_TAG="${BASE_VERSION}"
fi
else
SOURCE_TAG="latest"
fi
echo " Source: ${SOURCE_TAG}"
echo " Target: ${TAG_VERSION}"
# Check if source image exists
if crane manifest "${FULL_IMAGE}:${SOURCE_TAG}" >/dev/null 2>&1; then
echo " ✅ Source image exists, retagging..."
if crane tag "${FULL_IMAGE}:${SOURCE_TAG}" "${TAG_VERSION}"; then
echo " ✅ Successfully retagged from ${SOURCE_TAG}"
echo "needs_build=false" >> $GITHUB_OUTPUT
else
echo " ⚠️ Retag failed unexpectedly, will build instead"
echo "needs_build=true" >> $GITHUB_OUTPUT
fi
else
echo " ⚠️ Source image ${SOURCE_TAG} not found (may still be building)"
echo " 📦 Will build fresh instead of using stale fallback"
echo "needs_build=true" >> $GITHUB_OUTPUT
fi
# Fallback build if retag not possible
- name: Checkout repository
if: steps.retag-or-build.outputs.needs_build == 'true'
uses: actions/checkout@v6
- name: Set up Docker Buildx
if: steps.retag-or-build.outputs.needs_build == 'true'
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
if: steps.retag-or-build.outputs.needs_build == 'true'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
if: steps.retag-or-build.outputs.needs_build == 'true'
uses: docker/setup-qemu-action@v4
- name: Determine Dockerfile path
if: steps.retag-or-build.outputs.needs_build == 'true'
id: dockerfile
run: |
if [ -f "${{ env.AGENT_DIR }}/build/Dockerfile.a2a" ]; then
echo "path=${{ env.AGENT_DIR }}/build/Dockerfile.a2a" >> $GITHUB_OUTPUT
else
echo "path=build/agents/Dockerfile.a2a" >> $GITHUB_OUTPUT
fi
- name: Determine Agent Package Name
if: steps.retag-or-build.outputs.needs_build == 'true'
id: agent_package
run: |
if [ "${{ matrix.agent }}" == "template" ]; then
echo "name=petstore" >> $GITHUB_OUTPUT
else
echo "name=${{ matrix.agent }}" >> $GITHUB_OUTPUT
fi
- name: 🔨 Build and Push (fallback)
if: steps.retag-or-build.outputs.needs_build == 'true'
uses: docker/build-push-action@v7
with:
context: .
file: ${{ steps.dockerfile.outputs.path }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.determine-agents.outputs.tag_version }}
build-args: |
AGENT_NAME=${{ matrix.agent }}
AGENT_PACKAGE=${{ steps.agent_package.outputs.name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
# Notify release-finalize workflow when this CI completes (for final release builds only)
notify-release:
runs-on: ubuntu-latest
needs: [determine-agents, build-and-push, retag-unchanged]
# Only notify for final releases (non-RC tags) - RC builds don't need release finalization
if: |
always() &&
needs.determine-agents.outputs.tag_version != '' &&
!contains(needs.determine-agents.outputs.tag_version, '-rc.')
steps:
- name: 🔔 Notify release finalize
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_VERSION: ${{ needs.determine-agents.outputs.tag_version }}
run: |
# Determine overall conclusion
BUILD_RESULT="${{ needs.build-and-push.result }}"
RETAG_RESULT="${{ needs.retag-unchanged.result }}"
# Consider skipped as success (job conditions not met)
if [[ "$BUILD_RESULT" == "skipped" ]]; then BUILD_RESULT="success"; fi
if [[ "$RETAG_RESULT" == "skipped" ]]; then RETAG_RESULT="success"; fi
if [[ "$BUILD_RESULT" == "success" && "$RETAG_RESULT" == "success" ]]; then
CONCLUSION="success"
else
CONCLUSION="failure"
fi
echo "🔔 Notifying release-finalize: tag=$TAG_VERSION, conclusion=$CONCLUSION"
gh api /repos/${{ github.repository }}/dispatches --input - <<EOF
{
"event_type": "ci-completed",
"client_payload": {
"tag": "$TAG_VERSION",
"workflow": "${{ github.workflow }}",
"conclusion": "$CONCLUSION"
}
}
EOF