name: run-poc description: Run a full Proof-of-Concept pipeline for a GitHub repository on OpenShift AI. Clones the repo, analyzes it, forks to GitLab/GitHub, creates a PoC plan, containerizes with UBI images, builds and pushes to Quay, deploys to Kubernetes, runs validation tests, and generates a PoC report. Use this skill when asked to "run a PoC", "deploy a project to OpenShift", or given a GitHub URL to evaluate.
run-poc
Automated Proof-of-Concept pipeline for deploying GitHub projects on OpenShift AI (Open Data Hub). Takes a repository URL and project name, then executes an 11-phase pipeline that ends with a validated deployment and comprehensive report.
⚠️ CRITICAL EXECUTION REQUIREMENTS
THIS PIPELINE MUST RUN TO COMPLETION:
- COMPLETE ALL 11 PHASES: The pipeline has 11 sequential phases that must ALL be completed
- NO EARLY TERMINATION: Do not stop, summarize, or describe "what would happen next" — execute every phase
- NO PHASE SKIPPING: Each phase must be completed before proceeding to the next
- PROPER STATE TRACKING: Update the poc-state.yaml file after each completed phase
- EXPLICIT FAILURE HANDLING: If any phase fails, record the failure in state and exit with clear error message
Acceptable completion states:
- ✅ SUCCESS: All phases 1-11 completed with working deployment and generated report
- ❌ EXPLICIT FAILURE: Clear error at specific phase with failure reason recorded
- 🚫 UNACCEPTABLE: Stopping mid-pipeline to describe remaining phases
Before You Start
Read the state file if it exists:
$AUTOPOC_WORK_DIR/poc-state.yaml(default work dir:/workspacein pods,/tmp/autopoclocally). If a state file exists with completed phases, resume from the last incomplete phase.Verify credentials are available as environment variables:
- LLM:
ANTHROPIC_API_KEYor (VERTEX_PROJECT+VERTEX_LOCATION) - Fork target:
GITLAB_URL+GITLAB_TOKEN+GITLAB_GROUP(orGITHUB_TOKEN+GITHUB_ORG) - Registry:
QUAY_ORG+QUAY_TOKEN+QUAY_USERNAME - Cluster:
OPENSHIFT_TOKEN+OPENSHIFT_API_URL(optional if in-cluster)
- LLM:
Set up the work directory:
WORK_DIR="${AUTOPOC_WORK_DIR:-/tmp/autopoc}" mkdir -p "$WORK_DIR/repos"
Pipeline Overview
Phase 1: Intake -> Clone and analyze the repository [MANDATORY]
Phase 2: Evaluate -> Score RHOAI fitness [NON-BLOCKING]
Phase 3: Fork -> Push to GitHub/GitLab [MANDATORY]
Phase 4: PoC Plan -> Create plan with scenarios [MANDATORY]
Phase 5: Containerize -> Write UBI-based Dockerfiles [MANDATORY]
Phase 6: Build -> Build + push images to Quay [MANDATORY]
Phase 7: Deploy -> Generate Kubernetes manifests [MANDATORY]
Phase 8: Apply -> kubectl apply + verify pods [MANDATORY]
Phase 9: PoC Execute -> Write and run test scripts [MANDATORY]
Phase 10: PoC Report -> Generate markdown report [NON-BLOCKING]
Phase 11: Blog Post -> Generate developer blog (if tests pass) [NON-BLOCKING]
Phase Classification
MANDATORY phases MUST succeed before continuing. If a mandatory phase fails and retries are exhausted, STOP the pipeline immediately. Do NOT simulate, skip, or pretend the phase succeeded. Write the error to poc-state.yaml and exit.
NON-BLOCKING phases may fail without stopping the pipeline. If they fail, note the failure in state and continue to the next phase.
Retry loops
- Build retry: Phase 6 fails -> back to Phase 5 (fix Dockerfile) -> Phase 6 again
- Deploy retry: Phase 8 fails with manifest issue -> back to Phase 7 (fix manifests) -> Phase 8
- Container fix: Phase 8 or 9 detects container issue -> back to Phase 5 (full rebuild cycle)
Read references/retry-strategy.md for the complete retry logic.
State File
Maintain a progressive YAML state file at $WORK_DIR/poc-state.yaml. Read references/state-schema.md for the complete schema.
Update the state file after completing each phase. This is critical for:
- Tracking retry counters
- Providing context to later phases
- Enabling coarse resume if the pipeline is interrupted
Phase 1: Intake
Purpose: Clone the repository and analyze its structure, components, and technologies.
Steps
Clone the repository:
git clone "$REPO_URL" "$WORK_DIR/repos/$PROJECT_NAME"Explore the repository to understand its structure. Look at:
- File tree (use
lsorfindto see the layout) - README.md (read it for project description, setup instructions, usage)
- Build files (requirements.txt, package.json, go.mod, pyproject.toml, pom.xml, Cargo.toml)
- Entry points (main.py, app.py, server.py, index.js, main.go, Dockerfile CMD/ENTRYPOINT)
- Existing Dockerfiles and docker-compose.yml
- CI/CD config (.github/workflows/, .gitlab-ci.yml)
- Helm charts (Chart.yaml) or Kustomize (kustomization.yaml)
- File tree (use
Identify components. Follow the analysis instructions in
references/intake.md. For each component, determine:- name, language, build_system, entry_point, port
- is_ml_workload (check for torch, tensorflow, transformers, etc.)
- source_dir, existing_dockerfile
Validate component paths: For each component, verify
source_direxists on disk:ls "$WORK_DIR/repos/$PROJECT_NAME/$SOURCE_DIR"If it doesn't exist, look for the correct directory and adjust.
Update
poc-state.yamlwith the intake results.
Exit condition
At least one component identified. If zero components found, set error and stop.
Phase 2: Evaluate
Purpose: Score the project's fitness as an OpenShift AI proof-of-concept.
This phase is NON-BLOCKING. If evaluation fails, continue the pipeline.
Steps
Read the strategy configuration files directly:
- Active strategy:
cat $AUTOPOC_DATA_DIR/strategy_config.yamlto get the strategy name - Strategy profile:
cat $AUTOPOC_DATA_DIR/strategies/<strategy-name>.yamlfor scoring dimensions - Strategy baseline:
cat $AUTOPOC_DATA_DIR/strategy-baseline.yamlfor strategy areas, capability labels, core products
- Active strategy:
Score the project against the strategy dimensions. Read the strategy YAML and the repo digest/summary from Phase 1. Score each dimension (audience_value, strategic_alignment, strategy_fit, platform_leverage, demo_potential) on a 0-20 scale.
Write the evaluation to
$WORK_DIR/repos/$PROJECT_NAME/.autopoc/rhoai-evaluation.md.Update
poc-state.yamlwith evaluation results.
Exit condition
Evaluation written (or skipped on failure). Pipeline continues regardless.
Phase 3: Fork
Purpose: Push the repository to the configured forge (GitHub or GitLab).
This phase is MANDATORY. If the fork fails, STOP the pipeline.
Steps
Check fork target and prepare for smart fork handling:
echo "Fork target: ${AUTOPOC_FORK_TARGET:-github}"If
AUTOPOC_FORK_TARGETisgithub(default):Step 2a: Check if repository already exists
# Check if repository already exists EXISTING_REPO=$(gh repo view "$GITHUB_ORG/$PROJECT_NAME" --json name,description 2>/dev/null || echo "")Step 2b: Handle existing repository
if [ -n "$EXISTING_REPO" ]; then echo "Repository $GITHUB_ORG/$PROJECT_NAME already exists" # Check if it's an AutoPoC repository using topics REPO_TOPICS=$(gh api "/repos/$GITHUB_ORG/$PROJECT_NAME/topics" --jq '.names[]' 2>/dev/null || echo "") IS_AUTOPOC=$(echo "$REPO_TOPICS" | grep -q "autopoc" && echo "true" || echo "false") if [ "$IS_AUTOPOC" = "true" ]; then echo "Existing repository is AutoPoC-created. Force-syncing with source..." # Force sync the existing AutoPoC repository cd "$WORK_DIR/repos/$PROJECT_NAME" git remote add source "$REPO_URL" 2>/dev/null || git remote set-url source "$REPO_URL" git fetch source SOURCE_BRANCH=$(git ls-remote --symref source HEAD | awk '/^ref:/ {sub("refs/heads/", "", $2); print $2}') git reset --hard "source/${SOURCE_BRANCH:-main}" git remote rename origin upstream 2>/dev/null || true git remote add origin "https://${GITHUB_TOKEN}@github.com/${GITHUB_ORG}/${PROJECT_NAME}.git" \ 2>/dev/null || git remote set-url origin "https://${GITHUB_TOKEN}@github.com/${GITHUB_ORG}/${PROJECT_NAME}.git" git push origin --all --force git push origin --tags --force echo "Force-sync completed" else echo "ERROR: Repository $GITHUB_ORG/$PROJECT_NAME exists but is not AutoPoC-created" echo "Cannot proceed without manual intervention" exit 1 fi else # Create new fork echo "Creating new fork..." gh repo fork "$OWNER/$REPO" --org "$GITHUB_ORG" --clone=false # Set AutoPoC topics on the new fork AUTOPOC_TOPICS='["autopoc", "poc", "automated-deployment", "openshift"]' echo "Setting AutoPoC topics on $GITHUB_ORG/$PROJECT_NAME..." gh api "/repos/$GITHUB_ORG/$PROJECT_NAME/topics" --method PUT --raw-field names="$AUTOPOC_TOPICS" || echo "Warning: Failed to set topics" # Configure remotes cd "$WORK_DIR/repos/$PROJECT_NAME" git remote rename origin upstream 2>/dev/null || true git remote add origin "https://${GITHUB_TOKEN}@github.com/${GITHUB_ORG}/${PROJECT_NAME}.git" git push origin --all --force git push origin --tags --force echo "New AutoPoC fork created and tagged" fiIf
AUTOPOC_FORK_TARGETisgitlab:Create the project on GitLab via REST API:
GROUP_ID=$(curl -s -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "$GITLAB_URL/api/v4/groups?search=$GITLAB_GROUP" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])") curl -s -X POST -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "$GITLAB_URL/api/v4/projects" \ -d "name=$PROJECT_NAME&namespace_id=$GROUP_ID&visibility=internal" cd "$WORK_DIR/repos/$PROJECT_NAME" git remote rename origin github 2>/dev/null || true git remote add origin "https://oauth2:${GITLAB_TOKEN}@${GITLAB_URL#https://}/${GITLAB_GROUP}/${PROJECT_NAME}.git" \ 2>/dev/null || git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${GITLAB_URL#https://}/${GITLAB_GROUP}/${PROJECT_NAME}.git" git push origin --all --force git push origin --tags --forceUpdate
poc-state.yamlwith fork URL and target.
Exit condition
Fork URL recorded in state.
Phase 4: PoC Plan
Purpose: Create a PoC plan with test scenarios and infrastructure requirements.
Steps
Read the repo digest, component list, and evaluation from state.
Generate the PoC plan following the detailed instructions in
references/poc-plan.md. This includes:- Classifying the project type (model-serving, rag, web-app, llm-app, etc.)
- Determining infrastructure requirements (GPU, PVC, sidecars, resource profile)
- Determining deployment model (Deployment vs Job vs CronJob)
- Detecting LLM API dependencies
- Defining 2-5 test scenarios
Write
poc-plan.mdto the repo root.Optionally run Vale linting:
vale --output=JSON "$WORK_DIR/repos/$PROJECT_NAME/poc-plan.md" 2>/dev/null || trueCommit to the
autopoc-artifactsbranch:cd "$WORK_DIR/repos/$PROJECT_NAME" CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) git stash --quiet 2>/dev/null || true git checkout -B autopoc-artifacts git add poc-plan.md git commit -m "Add PoC plan" --allow-empty git push origin autopoc-artifacts --force git checkout "$CURRENT_BRANCH" git stash pop --quiet 2>/dev/null || trueUpdate
poc-state.yamlwith poc_type, scenarios, infrastructure, and poc_plan_path.
Exit condition
PoC plan written with at least one scenario defined.
Phase 5: Containerize
Purpose: Create UBI-based Dockerfiles for each PoC component.
Steps
Clean up previous build failures (if this is a retry):
# Check if we're retrying (build_retries > 0 in state) BUILD_RETRIES=$(python3 -c "import yaml; state = yaml.safe_load(open('$WORK_DIR/poc-state.yaml')); print(state.get('retries', {}).get('build_retries', 0))" 2>/dev/null || echo "0") if [ "$BUILD_RETRIES" -gt 0 ]; then echo "Cleaning up previous build failure (retry #$BUILD_RETRIES)..." # Clean up based on build strategy BUILD_STRATEGY="${AUTOPOC_BUILD_STRATEGY:-podman}" if [ "$BUILD_STRATEGY" = "openshift" ]; then # Clean up OpenShift build resources BUILDS_NAMESPACE="${OPENSHIFT_NAMESPACE_PREFIX:-poc}-builds" echo "Cleaning OpenShift builds in namespace: $BUILDS_NAMESPACE" kubectl delete buildconfig,imagestream -l app="$PROJECT_NAME" -n "$BUILDS_NAMESPACE" --ignore-not-found=true kubectl delete pod -l openshift.io/build.name="$PROJECT_NAME" -n "$BUILDS_NAMESPACE" --ignore-not-found=true else # Clean up podman images (keep only most recent) echo "Cleaning local podman images for project: $PROJECT_NAME" IMAGES=$(podman images --filter "label=project=$PROJECT_NAME" --format "{{.ID}} {{.Created}} {{.Repository}}:{{.Tag}}" | sort -k2 -r) if [ -n "$IMAGES" ]; then echo "$IMAGES" | tail -n +2 | while read -r image_id created repo_tag; do echo "Removing old image: $repo_tag ($image_id)" podman rmi "$image_id" --force 2>/dev/null || true done fi fi # Clean up old build logs (keep only the most recent) if [ -d "$WORK_DIR/repos/$PROJECT_NAME" ]; then find "$WORK_DIR/repos/$PROJECT_NAME" -name "build-*.log" -type f | sort -V | head -n -1 | xargs rm -f 2>/dev/null || true fi echo "Build cleanup completed" fiRead the PoC plan from state for infrastructure requirements and deployment model.
For each component that is part of the PoC (check
poc_plan.poc_componentsif set, otherwise use all components):a. Read the component's existing Dockerfile (if any):
cat "$WORK_DIR/repos/$PROJECT_NAME/$COMPONENT_DIR/Dockerfile" 2>/dev/nullb. Read the component's dependency file (requirements.txt, package.json, etc.)
c. Generate
Dockerfile.ubifollowing the rules inreferences/containerize.mdandreferences/ubi-dockerfile-rules.md. Key rules:- Use UBI base images (see mapping table in references)
- Final USER must be 1001
- Add
chgrp -R 0 /opt/app-root && chmod -R g=u /opt/app-rootbefore final USER - Use dnf for full UBI images, microdnf for ubi-minimal
- No privileged ports (80 -> 8080, 443 -> 8443)
- Use CPU-only ML package variants unless GPU is needed
- Set correct CMD/ENTRYPOINT based on deployment_model
d. Write the Dockerfile:
# Write Dockerfile.ubi to the component's source directorye. Create
.dockerignoreif it doesn't exist.If this is a retry entry (build error or container fix from state), read the error context and fix the Dockerfile accordingly.
Commit and push:
cd "$WORK_DIR/repos/$PROJECT_NAME" git add -A git commit -m "Add UBI Dockerfiles for PoC" git push origin HEAD --forceUpdate
poc-state.yamlwith Dockerfile paths.
Exit condition
Every PoC component has a Dockerfile.ubi written and committed.
Phase 6: Build
Purpose: Build container images and push to Quay registry.
Build Strategy
Check the BUILD_STRATEGY environment variable to decide how to build:
openshift(default in pods): Useocto build on-cluster. The cluster does the build and pushes the image -- no local container runtime needed.podman(local/default if unset): Usepodmanto build locally, then push.
Steps
(Optional) Quay repos are auto-created on push. If you want to pre-create:
curl -s -X POST -H "Authorization: Bearer $QUAY_TOKEN" \ "https://${QUAY_REGISTRY:-quay.io}/api/v1/repository" \ -H "Content-Type: application/json" \ -d '{"repository":"'"$PROJECT_NAME-$COMPONENT"'","namespace":"'"$QUAY_ORG"'","visibility":"public"}' \ 2>/dev/null || trueDetermine the image tag:
IMAGE_TAG="${QUAY_REGISTRY:-quay.io}/$QUAY_ORG/$PROJECT_NAME-$COMPONENT:latest"If
BUILD_STRATEGYisopenshift:The builds namespace is derived from
OPENSHIFT_NAMESPACE_PREFIX(defaults topoc):BUILDS_NS="${OPENSHIFT_NAMESPACE_PREFIX:-poc}-builds"a. Create the builds namespace and registry push secret:
oc create namespace "$BUILDS_NS" --dry-run=client -o yaml | oc apply -f - oc create secret docker-registry autopoc-registry-push \ --docker-server="${QUAY_REGISTRY:-quay.io}" \ --docker-username="$QUAY_USERNAME" \ --docker-password="$QUAY_TOKEN" \ -n "$BUILDS_NS" --dry-run=client -o yaml | oc apply -f -b. Create a BuildConfig and start a binary build:
# Create the BuildConfig (oc new-build creates it + an ImageStream) oc new-build --name="$PROJECT_NAME-$COMPONENT" \ --binary --strategy=docker \ --to-docker --to="$IMAGE_TAG" \ --push-secret=autopoc-registry-push \ -n "$BUILDS_NS" 2>&1 || true # ignore "already exists" errors # Start binary build -- uploads local source, builds on cluster, pushes to registry oc start-build "$PROJECT_NAME-$COMPONENT" \ --from-dir="$WORK_DIR/repos/$PROJECT_NAME/$COMPONENT_DIR" \ --follow --wait \ -n "$BUILDS_NS"c. Push is automatic -- the BuildConfig pushes to the registry as part of the build.
If
BUILD_STRATEGYispodman(or unset):a. Log in to the registry:
echo "$QUAY_TOKEN" | podman login "${QUAY_REGISTRY:-quay.io}" -u "$QUAY_USERNAME" --password-stdinb. Build the image:
podman build -t "$IMAGE_TAG" -f Dockerfile.ubi "$WORK_DIR/repos/$PROJECT_NAME/$COMPONENT_DIR"c. Push the image:
podman push "$IMAGE_TAG"On build failure:
- Read the error output
- Check if it's a permanent error (authentication failure, network unreachable) -> FAIL
- Check if retries are available (read
retries.build_retriesfrom state <retries.max_build_retries) - If retriable: increment
retries.build_retries, record the error, go back to Phase 5 - If retries exhausted: FAIL
Update
poc-state.yamlwith built images.
Exit condition
All component images built and pushed successfully.
Phase 7: Deploy
Purpose: Generate Kubernetes manifests for the deployment.
Steps
Clean up previous deployment failures (if this is a retry):
# Check if we're retrying (deploy_retries > 0 in state) DEPLOY_RETRIES=$(python3 -c "import yaml; state = yaml.safe_load(open('$WORK_DIR/poc-state.yaml')); print(state.get('retries', {}).get('deploy_retries', 0))" 2>/dev/null || echo "0") if [ "$DEPLOY_RETRIES" -gt 0 ]; then echo "Cleaning up previous deployment failure (retry #$DEPLOY_RETRIES)..." # Target namespace for the deployment DEPLOY_NAMESPACE="poc-$PROJECT_NAME" # Capture failure state before cleanup for debugging echo "Capturing failure state before cleanup..." kubectl get pods,deployments,services,configmaps,secrets -n "$DEPLOY_NAMESPACE" -o wide > "$WORK_DIR/failure-state-$DEPLOY_RETRIES.log" 2>/dev/null || true kubectl get events -n "$DEPLOY_NAMESPACE" --sort-by='.lastTimestamp' > "$WORK_DIR/failure-events-$DEPLOY_RETRIES.log" 2>/dev/null || true # Get pod logs before cleanup (for debugging) kubectl get pods -n "$DEPLOY_NAMESPACE" --no-headers 2>/dev/null | while read -r pod_name ready status restarts age; do if [ -n "$pod_name" ] && [ "$pod_name" != "No" ]; then echo "Capturing logs for pod: $pod_name" kubectl logs "$pod_name" -n "$DEPLOY_NAMESPACE" --all-containers=true > "$WORK_DIR/pod-logs-$pod_name-$DEPLOY_RETRIES.log" 2>/dev/null || true fi done # Clean up all deployment resources except namespace echo "Cleaning up deployment resources in namespace: $DEPLOY_NAMESPACE" kubectl delete deployments,services,configmaps,secrets,jobs,pods --all -n "$DEPLOY_NAMESPACE" --ignore-not-found=true --timeout=60s # Wait for cleanup to complete echo "Waiting for resource cleanup to complete..." sleep 10 # Verify namespace is clean REMAINING_RESOURCES=$(kubectl get all -n "$DEPLOY_NAMESPACE" --no-headers 2>/dev/null | wc -l) if [ "$REMAINING_RESOURCES" -gt 0 ]; then echo "Warning: $REMAINING_RESOURCES resources still remain in namespace" kubectl get all -n "$DEPLOY_NAMESPACE" else echo "Namespace successfully cleaned" fi echo "Deployment cleanup completed. Failure state preserved in $WORK_DIR/failure-*.log files" fiRead built images, PoC plan infrastructure, and scenarios from state.
Generate Kubernetes manifests following
references/deploy.md. Create files in$WORK_DIR/repos/$PROJECT_NAME/kubernetes/:namespace.yaml(always first)- For each component based on deployment_model:
deployment+listens_on_port: Deployment + Servicedeployment+ no port: Deployment only (no Service)job: One Job per test scenario
secret.yamlfor sensitive env vars (API keys, tokens)pvc.yamlif persistent storage needed
Handle LLM proxy if
infrastructure.needs_llm_apiis true:python -m autopoc.cli_tools llm-proxy '{"OPENAI_API_KEY": "required", "OPENAI_BASE_URL": ""}'Use the resolved env vars in the manifests.
Commit and push manifests:
cd "$WORK_DIR/repos/$PROJECT_NAME" git add kubernetes/ git commit -m "Add Kubernetes manifests" git push origin HEAD --forceIf this is a retry entry (apply error from state), read the previous error and fix the manifests accordingly.
Update
poc-state.yamlwith manifests directory path.
Exit condition
All manifest files written and committed.
Phase 8: Apply
Purpose: Apply manifests to the Kubernetes cluster and verify deployment.
Steps
Create the namespace:
kubectl create namespace "poc-$PROJECT_NAME" --dry-run=client -o yaml | kubectl apply -f -Apply manifests in order:
# Namespace first kubectl apply -f "$WORK_DIR/repos/$PROJECT_NAME/kubernetes/namespace.yaml" # Then PVCs, secrets, deployments, services kubectl apply -f "$WORK_DIR/repos/$PROJECT_NAME/kubernetes/" -n "poc-$PROJECT_NAME"Wait for rollout:
# For Deployments kubectl rollout status deployment/$COMPONENT -n "poc-$PROJECT_NAME" --timeout=300s # For Jobs kubectl wait --for=condition=complete job/$JOB_NAME -n "poc-$PROJECT_NAME" --timeout=120sVerify pods are healthy:
kubectl get pods -n "poc-$PROJECT_NAME"Wait 15 seconds, then check again for CrashLoopBackOff, ImagePullBackOff, etc.
Get service URLs:
kubectl get svc -n "poc-$PROJECT_NAME" -o jsonOn failure: Classify the error per
references/error-triage.md:- RBAC forbidden -> fix-manifest (back to Phase 7)
- Namespace not found -> fix-manifest (back to Phase 7)
- CrashLoopBackOff -> fix-dockerfile (back to Phase 5)
- ImagePullBackOff -> fix-dockerfile (back to Phase 5)
- Command not found in logs -> fix-dockerfile (back to Phase 5)
- Other -> analyze logs, classify, route accordingly
Check retry counters per
references/retry-strategy.md.Update
poc-state.yamlwith deployed resources, routes, and any errors.
Exit condition
All pods healthy and service URLs recorded.
Phase 9: PoC Execute
Purpose: Generate and run test scripts to validate the deployment.
Steps
Read scenarios and routes from state.
Write a Python test script (
poc_test.py) followingreferences/poc-execute.md:- Use only Python stdlib +
urllib.request - Implement each scenario from the PoC plan
- Include retry logic (services may take time to start)
- Output structured JSON results to stdout
- Use only Python stdlib +
Execute the test script:
cd "$WORK_DIR/repos/$PROJECT_NAME" python poc_test.py "$SERVICE_URL" 2>&1Parse the JSON results from stdout.
If tests fail, debug with kubectl:
kubectl get pods -n "poc-$PROJECT_NAME" kubectl logs deployment/$COMPONENT -n "poc-$PROJECT_NAME" --tail=50Detect container-level issues in test failures:
- "command not found" -> container fix (back to Phase 5)
- "ModuleNotFoundError" -> container fix (back to Phase 5)
- "CrashLoopBackOff" -> container fix (back to Phase 5)
- "exec format error" -> container fix (back to Phase 5)
Check
retries.container_fix_retries<retries.max_container_fix_retries.
Commit test artifacts to the
autopoc-artifactsbranch:cd "$WORK_DIR/repos/$PROJECT_NAME" CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) git stash --quiet 2>/dev/null || true git checkout -B autopoc-artifacts git add poc_test.py git commit -m "Add PoC test script" --allow-empty git push origin autopoc-artifacts --force git checkout "$CURRENT_BRANCH" git stash pop --quiet 2>/dev/null || trueUpdate
poc-state.yamlwith test results.
Exit condition
Test results recorded in state (pass or fail).
Phase 10: PoC Report
Purpose: Generate a comprehensive markdown report summarizing the entire pipeline run.
Steps
Read all state data (all phases).
Generate the report following
references/poc-report.md. Include:- Executive summary
- Project analysis with component table
- PoC objectives
- Pipeline execution summary (each phase)
- Test results table
- Infrastructure deployed
- Recommendations
- ODH/OpenShift AI considerations
- Appendix with links to artifacts
Write
poc-report.mdto the repo root.Add Mermaid diagrams to the report. For sections that describe architectures, flows, or deployment topologies, add inline mermaid code blocks. Typical diagrams for a PoC report:
- Pipeline flow: The phases that executed and their results
- Deployment topology: Namespace, pods, services, PVCs, and how they connect
- Component architecture: How the project's components interact (if multi-component)
Use the Red Hat brand theme:
```mermaid %%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#EE0000', 'primaryTextColor': '#fff', 'primaryBorderColor': '#A30000', 'lineColor': '#6A6E73', 'secondaryColor': '#F0F0F0', 'tertiaryColor': '#0066CC'}}}%% graph LR A[Phase 1: Intake] --> B[Phase 3: Fork] B --> C[Phase 5: Containerize] C --> D[Phase 6: Build] D --> E[Phase 8: Apply] E --> F[Phase 9: Test] ```Choose the right diagram type for the content:
graph TDorgraph LRfor architecture and flow diagramssequenceDiagramfor request/response or interaction flowsflowchartfor pipelines with decision pointsclassDiagramfor component relationships
Run Vale linting (optional):
vale --output=JSON "$WORK_DIR/repos/$PROJECT_NAME/poc-report.md" 2>/dev/null || trueCommit to the
autopoc-artifactsbranch:cd "$WORK_DIR/repos/$PROJECT_NAME" CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) git stash --quiet 2>/dev/null || true git checkout -B autopoc-artifacts git add poc-report.md git commit -m "Add PoC report" --allow-empty git push origin autopoc-artifacts --force git checkout "$CURRENT_BRANCH" git stash pop --quiet 2>/dev/null || trueUpdate
poc-state.yamlwith report path.
Exit condition
Report written and committed.
Phase 11: Blog Post (Conditional)
Purpose: Generate a developer blog post about the PoC deployment.
Only execute this phase if a majority of test scenarios passed.
Steps
- Count pass/fail from
poc_execute.resultsin state. - If majority passed: invoke the
blog-createskill with the PoC context. - The blog-create skill handles the content generation (draft, review loop, finalize).
- Run Vale linting on final blog post:
cd "$WORK_DIR/repos/$PROJECT_NAME/.autopoc/blog" vale --output=JSON final.md 2>/dev/null || true - MANDATORY — Upload to Google Docs (if GOOGLE_DOCS_FOLDER_ID is set):
Do NOT skip this step. After committing the blog to the artifacts branch,
run the upload command before moving on to sheet write-back or the next PoC.
Ifpython -m autopoc.cli_tools google-docs-upload \ "$WORK_DIR/repos/$PROJECT_NAME/.autopoc/blog/final.md" \ --project-name "$PROJECT_NAME"GOOGLE_DOCS_FOLDER_IDis not set, log a warning and continue.
Exit condition
Blog post written, linted, and uploaded to Google Docs (or upload skipped only if GOOGLE_DOCS_FOLDER_ID is not set). Phase is skipped entirely only if tests didn't pass.
Error Handling
CRITICAL: Never simulate, fake, or pretend a failed phase succeeded.
If a MANDATORY phase fails:
- Record the error in
poc-state.yaml(errors array + phase status = "failed") - STOP the pipeline immediately. Do not proceed to the next phase.
- Report what failed and why in your final output.
If a NON-BLOCKING phase fails (Evaluate, PoC Report, Blog Post):
- Record the failure in state
- Continue to the next phase
Phase classification reminder
| Phase | Classification |
|---|---|
| 1. Intake | MANDATORY |
| 2. Evaluate | NON-BLOCKING |
| 3. Fork | MANDATORY |
| 4. PoC Plan | MANDATORY |
| 5. Containerize | MANDATORY |
| 6. Build | MANDATORY |
| 7. Deploy | MANDATORY |
| 8. Apply | MANDATORY |
| 9. PoC Execute | MANDATORY |
| 10. PoC Report | NON-BLOCKING |
| 11. Blog Post | NON-BLOCKING |
General rules
- Always check exit codes after bash commands.
- Capture stderr alongside stdout for debugging:
command 2>&1 - For kubectl commands, always specify the namespace explicitly.
- If a command times out, record the timeout in the error and decide whether to retry.
Important Reminders
- Update poc-state.yaml after every phase. This is your persistent memory.
- Always use absolute paths for file operations and kubectl commands.
- Never leave USER root as the final Dockerfile directive.
- Check retry counters before looping back to a previous phase.
- Git operations: always push with
--forceto handle retry scenarios where commits are rewritten. - Never hallucinate success. If a build fails, it failed. If an image doesn't exist, don't deploy it. If a pod is crashing, don't pretend tests passed.