Adds the normative contract every platform agent must conform to, plus a
working reference template under template/ that ticks every box out of
the box.
Spec is derived from the production agents shipped in
cjot-backstage-az/agents/ on branch sandbox/jonathan:
- agent-registry, agent-factory, decomposer, discovery-agent,
golden-path, modernization-factory, modernization-factory-v2,
policy-transformer, scaffold-agent, support-intake-agent, the-watcher
Contents:
- SPEC.md normative contract (13 sections + conformance checklist)
- CHANGELOG.md spec versioning (1.0.0)
- docs/
registration.md self-registration with the agent-gateway
observability.md OTel logs/metrics/traces wiring
manifest.md /.well-known/agent.json schema + AgentSkill
serialisation (pydantic vs protobuf)
kubernetes.md k8s deployment shape, mandatory cross-refs,
per-namespace agents, resource sizing
deployment.md .image-version, ACR build, deploy scripts, rollback
deviations.md tracked debts against the spec
- template/
.env.local.example, .gitignore, .image-version (0.1.0),
Dockerfile (python:3.12-slim, non-root UID 1001, HEALTHCHECK),
requirements.txt (harness floor + optional LLM stack),
app/agent.py (Starlette entry, lifespan + self-registration,
defensive _skill_dict for pydantic vs protobuf),
app/logging_setup.py (canonical OTel log bridge — copy verbatim),
app/metrics.py (meter + example counter/histogram),
app/skills.py (AGENT_CONFIG + AgentSkill list),
k8s/configmap.yaml + deployment.yaml (Deployment + Service,
OTel annotations + 6-var env block,
agents-sa + agents-kv-spc bindings),
scripts/deploy.sh (auto-bump + az acr build + apply + rollout),
scripts/full-deploy.sh (preflight + deploy + post-deploy smoke),
scripts/deploy-local.sh (docker/podman + .env.local)
4.3 KiB
Build & deployment
Normative requirements: SPEC.md §10
Every agent ships three deploy scripts under scripts/ that wrap the same build → push → apply → roll-out pipeline at three layers of fidelity.
.image-version
A single-line file at the agent root:
1.0.3
Conventions:
- Semver only.
- MUST match
OTEL_RESOURCE_ATTRIBUTES'service.versionvalue at all times. - Bumped whenever a code change ships. The patch number is auto-incremented by
scripts/deploy.shwhen no--tagis given. - Pure image-version-only commits are reserved for redeploy reruns (e.g. rebuilding without code changes to refresh dependencies). They SHOULD be rare.
ACR
All images are pushed to:
bstagecjotdevacr.azurecr.io/<agent-name>:<X.Y.Z>
bstagecjotdevacr.azurecr.io/<agent-name>:latest
Both tags are pushed every build so that pinning to :latest works for sandbox/dev clusters and pinning to :<version> works for prod clusters.
scripts/deploy.sh
The fast path. Used for routine redeploys.
#!/bin/bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ACR_NAME="bstagecjotdevacr"
APP_NAME="<agent-name>"
NAMESPACE="agents"
VERSION_FILE="${SCRIPT_DIR}/../.image-version"
TAG=""
while [[ "$#" -gt 0 ]]; do
case $1 in
--tag) TAG="$2"; shift ;;
*) echo "unknown arg: $1"; exit 2 ;;
esac
shift
done
if [ -z "$TAG" ]; then
CURRENT=$(cat "$VERSION_FILE" 2>/dev/null || echo "1.0.0")
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
PATCH=$((PATCH + 1))
TAG="${MAJOR}.${MINOR}.${PATCH}"
echo "$TAG" > "$VERSION_FILE"
fi
IMAGE="${ACR_NAME}.azurecr.io/${APP_NAME}:${TAG}"
echo "Building ${IMAGE} ..."
az acr build --registry "$ACR_NAME" \
--image "${APP_NAME}:${TAG}" \
--image "${APP_NAME}:latest" \
"${SCRIPT_DIR}/.."
echo "Applying manifests ..."
kubectl apply -f "${SCRIPT_DIR}/../k8s/configmap.yaml"
kubectl apply -f "${SCRIPT_DIR}/../k8s/deployment.yaml"
kubectl -n "$NAMESPACE" rollout status "deployment/${APP_NAME}" --timeout=180s
Behaviour:
- Auto-bumps the patch on
.image-versionif--tagis not given. - Rebuilds and re-pushes both
:<tag>and:latest. - Applies
configmap.yamlanddeployment.yamlin order. - Blocks until the rollout completes (or fails) within 180 s.
scripts/full-deploy.sh
The "first time" path. Adds preflight + post-deploy smoke.
Preflight (refuse to continue if any check fails):
kubectlavailable and pointed at a cluster.azavailable and logged in.- Target namespace exists.
- ServiceAccount
agents-saexists in the namespace. - SecretProviderClass
agents-kv-spcexists in the namespace.
After rollout status succeeds:
kubectl exec deployment/<agent> -- curl -fs http://localhost:<port>/healthkubectl exec deployment/<agent> -- curl -fs http://localhost:<port>/.well-known/agent.json | jq -e '.name'
Failures abort with a non-zero exit code so CI can block.
scripts/deploy-local.sh
For dev loop on a workstation without cluster access.
- Builds the image locally with
dockerorpodman. - Reads
.env.local(sibling of.env.local.example). - Runs the container with
-p <port>:<port>and--env-file.
Useful for iterating on prompt changes without touching the cluster.
CI integration
The platform's GitHub Actions / Gitea Actions runners invoke scripts/deploy.sh --tag $GITHUB_SHA (or similar) when an agent's directory changes on the deploy branch. Pull requests trigger a build-only run that pushes a :<sha> tag without applying manifests.
Rolling back
There is no automated rollback. To roll back:
kubectl -n agents set image deployment/<agent-name> \
<agent-name>=bstagecjotdevacr.azurecr.io/<agent-name>:<previous-version>
kubectl -n agents rollout status deployment/<agent-name> --timeout=180s
If the rollback ships a different service.version, also revert OTEL_RESOURCE_ATTRIBUTES in the ConfigMap or the dashboards will misattribute. Prefer a forward-fix (new patch version) over a rollback whenever possible.
Re-registration after redeploy
The agent re-registers with the gateway on every pod start. No manual step is required after a rollout. If the gateway pod is also restarting at the same time, expect 1–2 WARNINGs in the agent log before registration succeeds — see registration.md.