OpenShell E2E Test Graph Loop

SkillMonitoring & ops

TDD iteration loop across 4 environments (local Kind, custom HyperShift, CI Kind, CI HyperShift) with test matrix tracking and log analysis

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the OpenShell E2E Test Graph Loop skill

What this skill tells your AI

The instructions your AI receives, as published by rossoctl/rossoctl in .claude/skills/graph-loop/SKILL.md and read by ahel’s review.

Iterate on OpenShell E2E tests across all 4 environments until the test matrix is green. Track iterations, detect regressions, and report status tables.

CRITICAL: Idempotency & Forward Progress

This skill is designed for /loop — it MUST be idempotent and always progress forward.

Rules:

  1. Check state first. Before running anything, read $LOG_DIR/test-matrix-tracking.md (or create it). Know which iteration you're on and what passed last time.
  2. Never re-run passing tests. If a test category passed in the previous iteration and no code changed, skip re-running it — mark as PASS (carry forward).
  3. Only fix, never regress. Before committing a fix, run targeted tests to verify the fix works AND doesn't break previously-passing tests. If a commit causes regression, revert it immediately.
  4. Track flaky tests. If a test passes sometimes and fails sometimes (same code), mark it as FLAKY in the matrix. Document the flakiness pattern in the tracking file. Flaky tests need root-cause analysis, not retries.
  5. Forward-only iteration counter. Each iteration number is monotonically increasing. Never reuse an iteration number. If you need to re-run, increment.
  6. Resume from where you left off. If the loop was interrupted, read the tracking file and continue from the last incomplete iteration. Don't restart from scratch.
  7. Show the matrix. Every iteration MUST end with the full matrix table printed to the user, showing all 4 environments and all categories.

Two-Speed Loop

The graph loop has two modes — use the quick debug loop to fix individual failures fast, then switch to the full iteration to verify everything.

Quick Debug Loop (inner loop — seconds to minutes)

For fixing specific failing tests on a LIVE cluster. No full redeploy.

  1. Identify the failing test from the matrix
  2. Redeploy only the affected component:
    # LiteLLM config change:
    kubectl apply -f - <<EOF ... EOF && kubectl rollout restart deploy/litellm-model-proxy -n team1
    
    # Test code change (no redeploy needed — pytest reads from disk):
    # just edit and rerun
    
    # Agent manifest change:
    kubectl apply -f deployments/openshell/agents/<agent>.yaml -n team1
    
    # Gateway change:
    kubectl delete sts openshell-gateway -n openshell-system --wait=false
    kubectl apply -k deployments/openshell/
    
  3. Run ONLY the failing tests:
    OPENSHELL_LLM_AVAILABLE=true uv run pytest \
      rossoctl/tests/e2e/openshell/test_12_litellm_claude_sandbox.py \
      -v --tb=short -k "test_name_pattern" \
      > $LOG_DIR/quick-debug.log 2>&1; echo "EXIT:$?"
    
  4. Check result — if it passes, run a slightly broader set to check regressions:
    OPENSHELL_LLM_AVAILABLE=true uv run pytest \
      rossoctl/tests/e2e/openshell/test_12_litellm_claude_sandbox.py \
      rossoctl/tests/e2e/openshell/test_07_skill_execution.py \
      -v --tb=short -k "claude or litellm or waypoint" \
      > $LOG_DIR/quick-regression.log 2>&1; echo "EXIT:$?"
    
  5. Commit the fix only when both targeted AND regression tests pass
  6. Return to full iteration to verify across all environments

Full Iteration (outer loop — 15-40 minutes)

Runs the complete openshell-full-test.sh end-to-end. Use AFTER quick debug fixes are committed. Produces the matrix row with all categories.

The flow:

Quick debug (fix A) → Quick debug (fix B) → Commit → Full iteration → Matrix update
     ↑                                                                      |
     └──────────── if regression detected ──────────────────────────────────┘

Environments

IDEnvironmentHow to runCredentials
kindLocal Kindopenshell-full-test.sh --skip-cluster-create --skip-cluster-destroy.env.maas
hcpCustom HyperShiftSame script with --platform ocp, uses KUBECONFIG=~/clusters/hcp/<cluster>/auth/kubeconfig.env.rossoctl-hypershift-custom + .env.maas
ci-kindCI Kind/run-e2e-openshell comment on PROPENAI_API_KEY GH secret
ci-hcpCI HyperShiftSame trigger, runs e2e-openshell-hypershift.yamlOPENAI_API_KEY GH secret

CRITICAL: CI Kind has TWO triggers — always use issue_comment run

The e2e-openshell-kind.yaml workflow fires on both pull_request and issue_comment. The pull_request run has NO secrets (fork PRs) so LLM tests skip (~79/0/65). The issue_comment run (from /run-e2e-openshell) has full secrets (~114/0/30).

Always analyze the issue_comment-triggered run, not the pull_request one:

# Find the CORRECT CI Kind run (issue_comment with secrets, not pull_request without)
CORRECT_RUN=$(gh run list --workflow e2e-openshell-kind.yaml --limit 10 \
  --json event,conclusion,databaseId \
  -q '[.[] | select(.event=="issue_comment" and .conclusion=="success")][0].databaseId')
gh run view "$CORRECT_RUN" --log 2>&1 | grep -E "PASSED|FAILED|SKIPPED|=====.*passed"

The pull_request run is useful only for infra-only validation (no LLM). For agent coverage (Claude Code, OpenCode, skills), the issue_comment run is authoritative.

Agent Capability Matrix (MANDATORY)

Every agent MUST be tested for the same baseline capabilities. The graph-loop matrix is organized as Capability × Agent × Model, not by test file. If an agent is missing a capability test, that is a gap to fix — not an expected skip.

Priority agents (must pass ALL capabilities)

  1. Claude Code (openshell_claude) — CLI sandbox
  2. OpenCode (openshell_opencode) — CLI sandbox
  3. OpenClaw (nemoclaw_openclaw) — NemoClaw gateway

Baseline agent capabilities (rows in the matrix)

Every agent must have tests for each of these (19 capabilities, 4 tiers):

Tier 1: Infrastructure (no LLM)

#CapabilityTest patternValidates
1Connectivitytest_connectivity__<agent>Agent responds to basic request
2Credential securitytest_credential_security__<agent>No hardcoded secrets
3Sandbox lifecycletest_sandbox_lifecycle__<agent>Create, list, delete sandbox
4Workspacetest_workspace__<agent>Data persists across pod restarts
5Resource limitstest_resource_limits__<agent>Respects CPU/memory budgets

Tier 2: Capabilities (requires LLM)

#CapabilityTest patternValidates
6Multiturntest_multiturn__<agent>Stateful 3+ turn conversation
7Context isolationtest_context_isolation__<agent>Sessions don't leak
8Session resumetest_session_resume__<agent>Survives pod restart
9Cross-session memorytest_cross_session_memory__<agent>Remembers previous sessions
10Streamingtest_streaming__<agent>Real-time response delivery
11Tool callingtest_tool_calling__<agent>Invokes tools (function calling)
12MCP directtest_mcp_direct__<agent>Calls MCP server directly
13MCP via gatewaytest_mcp_gateway__<agent>Calls MCP through gateway proxy
14MCP discoverytest_mcp_discovery__<agent>Discovers available MCP servers
15Concurrent sessionstest_concurrent_sessions__<agent>Multiple users don't interfere

Tier 3: Skills (per-model parametrized)

#CapabilityTest patternValidates
1Skill: PR reviewtest_skill_pr_review__<agent>__<model>LLM reviews code
2Skill: RCAtest_skill_rca__<agent>__<model>LLM diagnoses failures
3Skill: Securitytest_skill_security__<agent>__<model>LLM finds vulnerabilities
4Skill: GitHub PRtest_skill_github_pr__<agent>__<model>Clones and reviews live PR

Tier 4: Security & Policy

#CapabilityTest patternValidates
1HITL: Networktest_hitl_network__<agent>Unauthorized egress blocked
2HITL: Tool approvaltest_hitl_tool_approval__<agent>Requires permission before tool use
3HITL: MCP approvaltest_hitl_mcp__<agent>MCP server requires approval before executing
4Audit loggingtest_audit_logging__<agent>Actions produce OTel spans

Tier 7: Teleport (requires LLM + Sandbox CRD)

#CapabilityTest patternValidates
1Teleport: Packagetest_teleport__packageContext bundled into ConfigMap
2Teleport: Deploytest_teleport__deploySandbox created with context
3Teleport: Contexttest_teleport__context_unpackedCLAUDE.md unpacked in pod
4Teleport: Prompttest_teleport__prompt_with_contextClaude uses teleported context
5Teleport: Cleanuptest_teleport__cleanupResources cleaned up

MISS = test doesn't exist yet (gap to file as issue). SKIP with clear reason = acceptable temporarily. SKIP without reason = failure.

Design spec: docs/superpowers/specs/2026-05-02-agent-capability-test-matrix-design.md

Per-model parametrization (REQUIRED for skill tests)

All skill tests (PR review, RCA, security review, real GitHub PR) MUST be parametrized across the configured LLM models. The matrix shows results per model so we catch model-specific regressions.

Current models:

  • llama-scout-17b (primary, via LiteMaaS)
  • deepseek-r1 (deepseek-r1-distill-qwen-14b, via LiteMaaS)
  • mistral-small (mistral-small-24b, via LiteMaaS)

The test fixture receives the model name and passes it to the LLM call. LiteLLM proxy routes to the correct backend. Test names look like: test_pr_review__openshell_claude__llama_scout_17b test_pr_review__openshell_claude__deepseek_r1

MANDATORY Status Summary (print after EVERY iteration)

Every iteration MUST end with these 7 tables. Use for environments not run.

  1. Environment Totals — Pass/Fail/Skip/Total/Time per environment (4 rows)
  2. Capability × Agent — P/F/S/FL per cell, 4 env values: CI/CH/LK/HCP
  3. Per-Model Stats — tokens, time, quality per model (only if llm-metrics.json exists)
  4. Iteration Progress — trend arrows comparing last 4 iterations
  5. Agent Summaries — one-line verdict per agent
  6. Model Summaries — one-line verdict per model
  7. Failure RCA — every FAIL gets root cause + fix status

Legend: P=pass, F=fail, S=skip, —=miss, FL=flaky, CI=CI Kind, CH=CI HyperShift, LK=Local Kind, HCP=Custom HCP

One Iteration

Step 1: Run tests

Run on each available environment. Use background tasks for independence.

Local Kind (requires running Kind cluster with agents deployed):

export LOG_DIR=/tmp/rossoctl/tdd-iter<N> && mkdir -p $LOG_DIR
.github/scripts/local-setup/openshell-full-test.sh \
  --skip-cluster-create --skip-cluster-destroy \
  > $LOG_DIR/kind-fulltest.log 2>&1; echo "EXIT:$?"

Custom HyperShift (requires ospoc or similar cluster):

cd /path/to/main/repo  # NOT worktree — credentials live here
source .env.rossoctl-hypershift-custom
export KUBECONFIG=~/clusters/hcp/rossoctl-hypershift-custom-ospoc/auth/kubeconfig
/path/to/worktree/.github/scripts/local-setup/openshell-full-test.sh \
  --platform ocp --skip-cluster-create --skip-cluster-destroy \
  > $LOG_DIR/hcp-fulltest.log 2>&1; echo "EXIT:$?"

CI (push + comment):

git push
gh pr comment <PR> --body "/run-e2e-openshell"
# Wait for completion, then find the CORRECT run:
# IMPORTANT: CI Kind has two triggers. The pull_request run has NO secrets
# (LLM tests skip). Always use the issue_comment run for full coverage.
CI_KIND_RUN=$(gh run list --workflow e2e-openshell-kind.yaml --limit 10 \
  --json event,conclusion,databaseId \
  -q '[.[] | select(.event=="issue_comment" and .conclusion=="success")][0].databaseId')
gh run view "$CI_KIND_RUN" --log 2>&1 | grep -E "PASSED|FAILED|SKIPPED|=====.*passed" > $LOG_DIR/ci-kind-results.log

CI_HCP_RUN=$(gh run list --workflow e2e-openshell-hypershift.yaml --limit 5 \
  --json event,conclusion,databaseId \
  -q '[.[] | select(.conclusion=="success")][0].databaseId')
gh run view "$CI_HCP_RUN" --log 2>&1 | grep -E "PASSED|FAILED|SKIPPED|=====.*passed" > $LOG_DIR/ci-hcp-results.log

Step 2: Analyze results

Use a subagent per log file:

Agent(subagent_type='Explore'):
  "Read $LOG_DIR/<env>-fulltest.log. Report:
   1. Final pytest summary (passed/failed/skipped)
   2. ALL FAILED test names
   3. ALL tests matching: claude|opencode|adk|litellm|waypoint|gateway
   Use grep -E, do NOT read full file. Under 200 words."

Step 3: Build the matrix

Fill in the iteration row in the tracking file. Mark each category:

  • PASS — all tests in category pass
  • FAIL(N) — N tests fail (list which)
  • SKIP — all tests skip (note why)
  • BLOCK — environment unreachable or deploy failed

Step 4: Compare to previous iteration

Check if we improved:

  • New PASSes? → good
  • New FAILs? → regression, investigate immediately
  • Same FAILs? → root-cause and fix
  • More SKIPs? → check env/credential issues

Step 5: Fix and commit

Fix the root cause of failures. Commit with descriptive message. Do NOT commit if tests regressed from previous iteration.

Tracking File

Maintain at /tmp/rossoctl/test-matrix-tracking.md (or docs/plans/ for persistence).

Format:

# OpenShell E2E Test Matrix

## Iteration N — YYYY-MM-DD HH:MM
Commits: `<short-sha> <message>`

| Category | Local Kind | Custom HCP | CI Kind | CI HCP |
|----------|-----------|-----------|---------|--------|
| Waypoint | PASS | PASS | PASS | PASS |
| LiteLLM secure | PASS | SKIP | SKIP | SKIP |
| Anthropic passthrough | PASS | PASS | SKIP | SKIP |
| Claude Code sandbox | PASS | PASS | PASS | PASS |
| Claude Code skills | PASS(3/4) | SKIP | SKIP | SKIP |
| OpenCode sandbox | PASS | SKIP | SKIP | SKIP |
| ADK agent | PASS | PASS | PASS | SKIP |
| Claude SDK agent | PASS | PASS | PASS | PASS |
| Gateway | PASS | PASS | PASS | FAIL(5) |
| Platform | PASS | PASS | PASS | PASS |
| **Total** | **X/Y/Z** | **X/Y/Z** | **X/Y/Z** | **X/Y/Z** |

Changes from previous iteration:
- [+] Claude Code sandbox: SKIP → PASS (shared pod fix)
- [-] Gateway: PASS → FAIL (image pull issue on HCP)

Why tests skip in CI

Common causes and fixes:

SymptomCauseFix
All LLM tests skipOPENSHELL_LLM_AVAILABLE not setScript doesn't detect OPENAI_API_KEY from GH secrets — need .env.maas file fallback
Sandbox tests skipsandbox_crd_installed() returns FalseCRD not applied before pytest collection
OpenCode/Claude skiprun_*_in_sandbox() returns NoneSandbox pod failed to start — check image pull, namespace, secrets
ADK skip on HCPPort-forward failsSupervisor netns blocks — use port-bridge sidecar
Gateway tests fail on HCPGateway pod not runningImage pull auth, SCC, or StatefulSet immutable field

Loop Model

Run at least 5 iterations before giving up on aligning the matrix. Each iteration runs environments in parallel at their natural speed:

  • Fast lane: Local Kind + CI Kind (push triggers CI, run local simultaneously)
  • Slow lane: Custom HyperShift (deploy takes longer, run after local Kind validates)
  • Passive: CI HyperShift (triggered by same /run-e2e-openshell comment)

Iteration workflow:

  1. Fix — apply fixes from previous iteration's failures
  2. Commit + push — triggers CI Kind + CI HyperShift
  3. Run local Kind — background, parallel with CI
  4. Run custom HyperShift — background if cluster ready, otherwise after Kind
  5. Collect results — use subagents to parse all logs in parallel
  6. Update matrix — fill in iteration row, compare to previous
  7. Brainstorm — if same failures persist across iterations, use superpowers:brainstorming or superpowers:systematic-debugging to rethink the approach before the next iteration

After 5 iterations:

  • Show final matrix table grouped by test category
  • Highlight: what passes everywhere, what's environment-specific, what's flaky
  • Brainstorm with user on misaligned columns

Event Graph Validation

The "graph" in graph-loop means reconstructing the flow of events across components and validating the expected sequence occurred. This goes beyond pass/fail — it verifies the ARCHITECTURE is working correctly.

How it works:

  1. Collect logs from all components after a test run
  2. Reconstruct the event graph — trace a request through the system:
    Claude Code → ANTHROPIC_BASE_URL → LiteLLM /v1/messages
      → hosted_vllm translation → LiteMaaS /v1/chat/completions
      → response back through LiteLLM → Anthropic format → Claude Code
    
  3. Assert expected events present in the logs:
    • LiteLLM: POST /v1/messages received, model resolved, upstream call made
    • Gateway: sandbox created, pod scheduled, exec completed
    • Waypoint: HBONE connection established, no cert errors
  4. Flag missing events — if a step is missing, the architecture has a gap

Log levels:

  • INFO (default): sufficient for most validation — request/response logging
  • DEBUG: enable per-component when investigating specific failures:
    kubectl set env deploy/litellm-model-proxy -n team1 LITELLM_LOG=DEBUG
    kubectl set env deploy/claude-sdk-agent -n team1 -c agent LOG_LEVEL=DEBUG
    
  • Only enable DEBUG for the component under investigation, reset after

Example event graph assertion (for Claude Code sandbox test):

[litellm] INFO POST /v1/messages model=claude-sonnet-4-20250514
[litellm] INFO Using chat_completions path (anthropic messages translation)
[litellm] INFO Upstream call to hosted_vllm/llama-scout-17b
[litellm] INFO Response 200 tokens=N

If any line is missing → the test should flag it even if the response was correct.

Log Analysis (per iteration)

Every iteration must also analyze component logs for errors and warnings. Target: 0 errors, minimum warnings.

Collect logs (after test run, before cleanup):

for COMP in openshell-gateway litellm-model-proxy; do
  kubectl logs deploy/$COMP -n ${NS:-team1} --tail=500 > $LOG_DIR/${COMP}.log 2>&1 || true
done
for COMP in claude-sdk-agent adk-agent-supervised weather-agent-supervised; do
  kubectl logs deploy/$COMP -n team1 -c agent --tail=200 > $LOG_DIR/${COMP}.log 2>&1 || true
done
kubectl logs -n istio-system -l app=ztunnel --tail=100 > $LOG_DIR/ztunnel.log 2>&1 || true
kubectl logs deploy/waypoint -n team1 --tail=100 > $LOG_DIR/waypoint.log 2>&1 || true

Analyze with subagent:

Agent(subagent_type='Explore'):
  "Grep $LOG_DIR/*.log for ERROR|WARN|error|warn|panic|fatal.
   Categorize by component and severity.
   Exclude known noise: 'deprecated', 'liveness probe'.
   Report: component, count of errors, count of warnings, sample messages.
   Under 200 words."

Log matrix columns:

ComponentErrorsWarningsNotes
openshell-gateway02deprecation warnings (known)
litellm-model-proxy00clean
claude-sdk-agent01reconnect warning
ztunnel00clean
waypoint00clean

OTel structured logging

Verify agents emit structured JSON logs with OTel fields:

  • trace_id, span_id in log entries (when tracing enabled)
  • level, msg, component fields
  • No raw print() or unstructured output in production paths

Done condition

All 4 environments show:

  • Claude Code sandbox: PASS
  • OpenCode sandbox: PASS
  • ADK agent: PASS
  • Gateway: PASS
  • 0 FAIL, only expected SKIP (e.g., NemoClaw when not deployed)
  • 0 ERROR in component logs
  • Warnings catalogued and either fixed or documented as known

End-of-Cycle Review (after 5 iterations)

After 5 iterations or when progress stalls, present: final matrix, resolved items, remaining blockers with options, and batched questions for user to unblock the next cycle. User answers all at once → clear direction.

Signals

GitHub stars
300
Forks
107
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
graph-loop
Source
github.com/rossoctl/rossoctl