Skip to content
Back to skills

Test Props

BSecurity

Manual live props deployment testing — sets up Podman infrastructure (postgres, registry, backend) and runs real agent containers. NOT for standard Bazel tests (use `bazel test //props/...` for those).

  • 11 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 9, 2026
devopsbashsqldockertestinggitapidatabasebackend

Works with

  • claude code
  • cli
  • api

Security analysis

B80/100
  • mediumUses curl or wget to download content
  • criticalSends environment variables or credentials to an external URL

Pro scans all 5 files and shows the line behind each finding

Scanned September 9, 2026

npx -y skills add agentydragon/ducktape --skill test_props --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Test Props?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Test Props
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/agentydragon-test-props/badge)](https://www.skillsdirectory.com/skills/agentydragon-test-props)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: test_props
description: Manual live props deployment testing — sets up Podman infrastructure (postgres, registry, backend) and runs real agent containers. NOT for standard Bazel tests (use `bazel test //props/...` for those).
allowed-tools: Bash, Read, Grep, Glob, Edit, Write, WebFetch, Task
---

# Test Props Live Deployment

Knowledge base for manually running the props critic and grader evaluation stack.
Covers infrastructure setup, database lifecycle, agent image push, running critics,
grading, and exporting results.

**Not for standard tests** — use `bazel test //props/...` for unit, integration, and
e2e Bazel tests. This skill is for manual live deployment and evaluation runs.

For evaluation workflow background:

- @props/docs/openai_evaluation/evaluation.md
- @props/docs/local_llm_evaluation/evaluation.md

## Environment Detection

Detect gVisor (Claude Code on the Web) by kernel version:

```bash
if [[ "$(uname -r)" == "4.4.0" ]]; then
  IS_GVISOR=true
else
  IS_GVISOR=false
fi
```

gVisor requires:

- `PROPS_DOCKER_NETWORK=host` (agent containers must use host networking)
- `--annotation run.oci.keep_original_groups=1` on all podman containers
- Verify `$DOCKER_HOST` is set (the session hook sets this)

## Infrastructure Setup

Start Postgres (port 5433) and the OCI registry (port 5050):

```bash
bash .claude/skills/test_props/start_infra.sh
```

Set PG environment variables for subsequent commands:

```bash
export PGHOST=127.0.0.1
export PGPORT=5433
export PGUSER=postgres
export PGPASSWORD=$(cat props/.devenv/state/pg_password)
export PGDATABASE=eval_results
```

## Database Lifecycle

### Fresh schema

`db recreate` drops all schema objects (tables, views, functions, policies), then
runs Alembic migrations to recreate the schema from scratch and syncs model
metadata. **Does not sync specimens.**

```bash
PGHOST=127.0.0.1 PGPORT=5433 PGUSER=postgres \
PGPASSWORD=$(cat props/.devenv/state/pg_password) \
PGDATABASE=eval_results \
bazel run //props/cli:cli -- db recreate --yes
```

### Migrations and specimen sync via backend lifespan

When `auto_migrate = true` is set in the config file, the backend runs
`alembic upgrade head` on startup (idempotent — only applies pending migrations).

When `auto_sync_specimens = true` is set, the backend scans `/specimens` and syncs
all specimens on startup. Before starting the backend, symlink the repo's specimens
directory:

```bash
ln -sf "$(git rev-parse --show-toplevel)/props/specimens" /specimens
```

The `config.ollama.toml` in this skill directory enables both flags.

### Importing an existing dump

To continue from a saved dump (e.g., from a previous session):

1. Recreate schema (via `db recreate`).
2. Sync specimens — start backend with `auto_sync_specimens = true` (and the `/specimens`
   symlink in place), or run `bazel run //props/cli:cli -- db sync-specimen` per specimen.
3. Import the dump:

```bash
# OpenAI evaluation results:
zstd -dc props/docs/openai_evaluation/results.sql.zst \
  | psql --set ON_ERROR_STOP=on -d eval_results

# Local LLM (ollama) results:
zstd -dc props/docs/local_llm_evaluation/results.sql.zst \
  | psql --set ON_ERROR_STOP=on -d eval_results
```

The dump excludes ground-truth tables (snapshots, file_sets, true_positives, etc.)
that come from specimens. Specimens must be in the DB before importing.

## Backend Startup

### With remote Ollama (gpt-oss:20b)

Retrieve the Ollama API key from k8s:

```bash
export OLLAMA_API_KEY=$(kubectl get secret ollama-api-key -n claude-sandbox \
  -o jsonpath='{.data.api-key}' | base64 -d)
```

Symlink specimens and build:

```bash
ln -sf "$(git rev-parse --show-toplevel)/props/specimens" /specimens
bazel build //props/backend:backend_bin
```

Start in background:

```bash
PROPS_CONFIG_FILE=.claude/skills/test_props/config.ollama.toml \
PGHOST=127.0.0.1 PGPORT=5433 PGUSER=postgres \
PGPASSWORD=$(cat props/.devenv/state/pg_password) \
PGDATABASE=eval_results \
PROPS_REGISTRY_UPSTREAM_URL=http://127.0.0.1:5050 \
PROPS_DOCKER_NETWORK=host \
DOCKER_HOST=$DOCKER_HOST \
OLLAMA_API_KEY=$OLLAMA_API_KEY \
bazel-bin/props/backend/backend_bin serve > /tmp/backend.log 2>&1 &
```

The backend logs the admin token on startup:

```bash
grep "Admin token" /tmp/backend.log
```

Check health: `curl -s http://127.0.0.1:8000/health`

### With OpenAI

Use `config.podman.toml` and set `OPENAI_API_KEY` + `OPENAI_BASE_URL`:

```bash
PROPS_CONFIG_FILE=.claude/skills/test_props/config.podman.toml \
OPENAI_API_KEY=$OPENAI_API_KEY \
OPENAI_BASE_URL=https://api.openai.com/v1 \
PGHOST=127.0.0.1 PGPORT=5433 PGUSER=postgres \
PGPASSWORD=$(cat props/.devenv/state/pg_password) \
PGDATABASE=eval_results \
PROPS_REGISTRY_UPSTREAM_URL=http://127.0.0.1:5050 \
PROPS_DOCKER_NETWORK=host \
DOCKER_HOST=$DOCKER_HOST \
bazel-bin/props/backend/backend_bin serve > /tmp/backend.log 2>&1 &
```

## Push Agent Images

Push images to the **registry proxy** (port 8000), not the upstream registry
(port 5050). The proxy records agent definitions; the grader supervisor
listens for grader tag changes.

Set up Docker auth for the registry proxy:

```bash
PG_PASSWORD=$(cat props/.devenv/state/pg_password)
AUTH_B64=$(echo -n "postgres:$PG_PASSWORD" | base64)
mkdir -p ~/.docker
cat > ~/.docker/config.json <<EOF
{
  "auths": {
    "localhost:8000": { "auth": "$AUTH_B64" }
  }
}
EOF
```

Push both agent types:

```bash
bazel run //props/agents/critic:push
bazel run //props/agents/grader:push
```

These push to `localhost:8000/<type>:latest`. The grader supervisor starts grader
containers automatically when the grader image is pushed.

## Running Critics

Get the admin token and run a critic via the API. The call blocks until the
critic container exits.

```bash
PG_PASSWORD=$(cat props/.devenv/state/pg_password)
AUTH_TOKEN=$(echo -n "postgres:$PG_PASSWORD" | base64)

# File-set example: rank-1 example (33 TPs, 39 occurrences, fastest to run)
curl -s -X POST http://127.0.0.1:8000/api/runs/critic \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "definition_id": "latest",
    "example": {
      "kind": "file_set",
      "snapshot_slug": "ducktape/2025-09-03-00",
      "files_hash": "8e2209f20bd1df0c5bc4073dfff739fe"
    },
    "critic_model": "gpt-oss-20b",
    "timeout_seconds": 1800,
    "budget_usd": 0.0
  }'
```

Top file-set examples (file-set runs are faster than whole-snapshot):

| Rank | Snapshot                       | `files_hash`                       | TPs | Occurrences |
| ---- | ------------------------------ | ---------------------------------- | --- | ----------- |
| 1    | `ducktape/2025-09-03-00`       | `8e2209f20bd1df0c5bc4073dfff739fe` | 33  | 39          |
| 2    | `ducktape/2025-11-20-00`       | `bb8aff17944a6348a8089790457e3094` | 15  | 31          |
| 3    | `ducktape/2025-11-26-00`       | `6e416fb1d095abc7fdc79131434c7dac` | 20  | 21          |
| 4    | `ducktape/2025-11-21-00`       | `15702f4d16234db852e973e31323fbdd` | 21  | 21          |
| 5    | `gmail-archiver/2025-12-17-00` | `9e218584782810e5a65195da8f63931a` | 14  | 21          |

`budget_usd = 0.0` for Ollama (local cluster inference is free).

## Monitoring Runs

```bash
# All runs with status
psql -c "SELECT agent_run_id, type_config->>'agent_type' AS type, model, status,
         container_exit_code FROM agent_runs ORDER BY created_at"

# Reported issues for a specific critic run
psql -c "SELECT COUNT(*) FROM reported_issues WHERE agent_run_id = '<run_id>'"

# Grading edges (populated by grader after critic finishes)
psql -c "SELECT ge.critique_run_id, ge.critique_issue_id,
         ge.tp_id, ge.fp_id, ge.grade
         FROM grading_edges ge
         WHERE ge.critique_run_id = '<critic_run_id>'"

# Grading pending (count should reach 0 when grading is complete)
psql -c "SELECT COUNT(*) FROM grading_pending WHERE critique_run_id = '<critic_run_id>'"
```

## Grader Supervisor

The `GraderSupervisor` is enabled by `grader_model` in the config file. It starts
automatically when the backend starts and listens for `grader_definition_changed`
pg_notify events. When a grader image is pushed, it (re)starts grader containers
for all active snapshots.

Graders run continuously, watching for new critic runs to grade. After a critic
completes, the grader picks up the new reported issues and creates `grading_edges`.

If graders don't appear after pushing the image, check:

1. Was the image pushed to the proxy (port 8000), not directly to the registry (port 5050)?
2. Backend logs: `grep -i grader /tmp/backend.log`

## Exporting Results

Export run results (excluding ground-truth and infrastructure tables) for import
in a future session. The export excludes specimen data (snapshots, file_sets,
true_positives, etc.) since those come from specimens on import.

```bash
# For ollama/local LLM evaluation:
pg_dump eval_results \
  --data-only --no-owner --no-privileges \
  --exclude-table=true_positives \
  --exclude-table=true_positive_occurrences \
  --exclude-table=false_positives \
  --exclude-table=false_positive_occurrences \
  --exclude-table=fp_occurrence_relevant_files \
  --exclude-table=occurrence_ranges \
  --exclude-table=critic_scopes_expected_to_recall \
  --exclude-table=file_sets \
  --exclude-table=file_set_members \
  --exclude-table=snapshots \
  --exclude-table=snapshot_files \
  --exclude-table=model_metadata \
  --exclude-table=agent_role_salt \
  --exclude-table=alembic_version \
  -f props/docs/local_llm_evaluation/results.sql \
  && zstd --rm --ultra -22 props/docs/local_llm_evaluation/results.sql
```

`llm_requests` stores full conversation transcripts (O(N²) growth across turns).
zstd compresses cross-row redundancy far better than per-row gzip.

## Troubleshooting

### Image resolution errors

Add insecure registry entries to `~/.cache/claude-hooks/podman/registries.conf`:

```toml
[[registry]]
prefix = "127.0.0.1:5050"
location = "127.0.0.1:5050"
insecure = true

[[registry]]
prefix = "127.0.0.1:8000"
location = "127.0.0.1:8000"
insecure = true
```

### Password issues

Use hex-only passwords in `props/.devenv/state/pg_password` (no `/`, `+`, `=`
characters that break asyncpg DSN parsing).

### Ollama API key

Retrieve fresh from k8s if the current key doesn't work:

```bash
kubectl get secret ollama-api-key -n claude-sandbox \
  -o jsonpath='{.data.api-key}' | base64 -d
```

## Key Architecture Points

- **Registry proxy**: Integrated into the backend. Push images to port 8000
  (backend), which proxies to port 5050 (upstream registry) and records agent
  definitions.
- **Grader supervisor**: Listens for `grader_definition_changed` pg_notify.
  When a grader tag is pushed, all grader containers are (re)started.
- **Agent containers**: Run with host networking, per-agent PostgreSQL roles,
  and RLS-scoped database access.
- **Model config**: `config.ollama.toml` (this skill directory) configures
  `gpt-oss:20b` via the remote LiteLLM cluster at `litellm.allegedly.works`.
  The cluster serves the model with 131072-token context (`OLLAMA_NUM_CTX=131072`).

Files in this skill

  • SKILL.md10.8 KB
  • compose.yaml771 B
  • config.ollama.toml1.4 KB
  • config.podman.toml544 B
  • start_infra.sh1.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.