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)
134 lines
4.3 KiB
Markdown
134 lines
4.3 KiB
Markdown
# Build & deployment
|
||
|
||
> Normative requirements: [SPEC.md §10](../SPEC.md#10-build--deployment-must)
|
||
|
||
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.version` value at all times.
|
||
- Bumped whenever a code change ships. The patch number is auto-incremented by `scripts/deploy.sh` when no `--tag` is 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.
|
||
|
||
```bash
|
||
#!/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-version` if `--tag` is not given.
|
||
- Rebuilds and re-pushes both `:<tag>` and `:latest`.
|
||
- Applies `configmap.yaml` and `deployment.yaml` in 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):
|
||
|
||
- `kubectl` available and pointed at a cluster.
|
||
- `az` available and logged in.
|
||
- Target namespace exists.
|
||
- ServiceAccount `agents-sa` exists in the namespace.
|
||
- SecretProviderClass `agents-kv-spc` exists in the namespace.
|
||
|
||
After `rollout status` succeeds:
|
||
|
||
- `kubectl exec deployment/<agent> -- curl -fs http://localhost:<port>/health`
|
||
- `kubectl 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 `docker` or `podman`.
|
||
- 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:
|
||
|
||
```bash
|
||
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 `WARNING`s in the agent log before registration succeeds — see [registration.md](registration.md#failure-modes--resolution).
|