Files
agent-harness-spec/docs/deployment.md
Jonathan Boniface e6f10ba8c9 feat: initial harness spec v1.0.0 + reference template
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)
2026-06-09 15:54:18 +01:00

4.3 KiB
Raw Blame History

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.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.

#!/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:

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 12 WARNINGs in the agent log before registration succeeds — see registration.md.