Files
member-console/scripts/agent-runner/run-ux.sh
T
cgalo5758 5829091881 Track the agent runner and its method page
The contained runner that drove the 2026-09 security audit, the README
review and four rounds of design ideation lived only inside the ignored
notebook. It moves to scripts/agent-runner/: the prepare, tools, audit,
ideation and teardown scripts, the compose and container files, the
allowlist proxy and the prompt templates, with the paths that assumed
the notebook fixed and findings written to a caller-named directory. Run
outputs, transcripts and the round-specific sheet scripts stay behind.

docs/agent-runner.md states the method: the disposable git archive
copy and the fail-closed proxy, how a task is shaped, union rather than
intersection of findings across models, separate adjudication of every
finding against the source, and the evidence a finding must carry.
2026-09-19 19:47:15 -05:00

99 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Ideation lane: run outside models on a design brief, one lane per provider,
# inside the same contained container the security lane uses. The container
# sees a disposable git-archive copy of HEAD and can reach only the model APIs.
#
# The brief is yours, not the runner's. Pass the directory holding it; it must
# contain DESIGNER.md (how the model works and what it must produce),
# CONTEXT.md (the surface and its constraints), SCREENS.md (what exists today)
# and brief.md (the deliverable). Override any one with the matching variable.
#
# Usage:
# ./prepare.sh # once: export HEAD, build, start proxy
# ./run-ux.sh <brief-dir> # default four models
# MODELS="zai-coding-plan/glm-5.3" ./run-ux.sh <brief-dir> # a subset
# TIMEOUT=3600 VARIANT=high ./run-ux.sh <brief-dir> # budget, reasoning effort
# OUT=<dir> ./run-ux.sh <brief-dir> # findings dir; default <brief-dir>/out
# EXTRA=<file> INCLUDE="<file> ..." ./run-ux.sh <brief-dir>
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
REPO="$(git -C "$HERE" rev-parse --show-toplevel)"
BRIEF_DIR="${1:?usage: OUT=<dir> ./run-ux.sh <brief-dir>}"
BRIEF_DIR="$(cd "$BRIEF_DIR" && pwd)"
[[ -d "$HERE/.code" ]] || { echo "No $HERE/.code: run $HERE/prepare.sh first." >&2; exit 1; }
DESIGNER="${DESIGNER:-$BRIEF_DIR/DESIGNER.md}"; CONTEXT="${CONTEXT:-$BRIEF_DIR/CONTEXT.md}"
SCREENS="${SCREENS:-$BRIEF_DIR/SCREENS.md}"; BRIEF="${BRIEF:-$BRIEF_DIR/brief.md}"
for f in "$DESIGNER" "$CONTEXT" "$SCREENS" "$BRIEF"; do
[[ -f "$f" ]] || { echo "missing $f" >&2; exit 1; }
done
# Findings land where the caller says; never in this directory, which is tracked.
OUT="${OUT:-$BRIEF_DIR/out}"; mkdir -p "$OUT"; OUT="$(cd "$OUT" && pwd)"; export OUT
DEFAULT_MODELS="deepseek/deepseek-v4-pro google/gemini-3.8-flash zai-coding-plan/glm-5.3 kimi-for-coding/k3-256k"
MODELS="${MODELS:-$DEFAULT_MODELS}"
TIMEOUT="${TIMEOUT:-2700}"
VARIANT="${VARIANT-max}"
VARIANT_FLAG=""; [[ -n "$VARIANT" ]] && VARIANT_FLAG="--variant $VARIANT"
RUN_DIR="$OUT/$(date +%Y-%m-%d_%H%M%S)"
mkdir -p "$RUN_DIR/_static"
# bootstrap.css from the snapshot; app.css from the working tree, so a mockup
# renders against the stylesheet the change is being made under.
cp "$HERE/.code/internal/embeds/static/bootstrap.css" "$REPO/internal/embeds/static/app.css" "$RUN_DIR/_static/"
# The brief travels inside the disposable copy, in a per-run directory so two
# runs with different briefs can overlap. EXTRA (a file) is appended to the
# prompt after brief.md; INCLUDE (space-separated files) travel alongside for
# the model to read, and are named at the end of the prompt.
UX="ux-$(basename "$RUN_DIR")"
mkdir -p "$HERE/.code/$UX"
cp "$DESIGNER" "$HERE/.code/$UX/DESIGNER.md"; cp "$CONTEXT" "$HERE/.code/$UX/CONTEXT.md"; cp "$SCREENS" "$HERE/.code/$UX/SCREENS.md"; cp "$BRIEF" "$HERE/.code/$UX/brief.md"
EXTRA_CAT=""; INCLUDE_LINE=""
if [[ -n "${EXTRA:-}" ]]; then cp "$EXTRA" "$HERE/.code/$UX/extra.md"; EXTRA_CAT="; echo; echo '---'; cat $UX/extra.md"; fi
if [[ -n "${INCLUDE:-}" ]]; then
mkdir -p "$HERE/.code/$UX/include"; names=""
for f in $INCLUDE; do cp "$f" "$HERE/.code/$UX/include/"; names="$names $UX/include/$(basename "$f")"; done
INCLUDE_LINE="; echo; echo 'Files to read first, in this copy:$names'"
fi
{
echo "commit $(cat "$HERE/.code/AUDIT_COMMIT.txt")"
echo "models $MODELS"
echo "brief $(basename "$BRIEF") context $(basename "$CONTEXT") screens $(basename "$SCREENS")${EXTRA:+ + extra $(basename "$EXTRA")}${INCLUDE:+ + include $INCLUDE}"
echo "variant ${VARIANT:-none}"
echo "timeout ${TIMEOUT}s per lane"
echo "started $(date -Is)"
} > "$RUN_DIR/manifest.txt"
compose() { docker compose -f "$HERE/compose.yaml" --project-directory "$HERE" "$@"; }
compose up -d egress-proxy >/dev/null
run_lane() {
local model="$1" safe="${1//\//__}"
local outdir="$RUN_DIR/$safe"; mkdir -p "$outdir"
echo "==> [$model] started $(date +%H:%M:%S)"
# Fresh --rm container per model. --no-deps keeps the proxy as it is. -T: no
# TTY. stdin closed (opencode headless quirk). The prompt is the four brief
# files plus one line naming this lane's output directory.
if ! timeout "$TIMEOUT" docker compose -f "$HERE/compose.yaml" --project-directory "$HERE" run --rm --no-deps -T -v "$RUN_DIR:/ux-out" auditor \
bash -c "opencode run --model '$model' $VARIANT_FLAG \"\$(cat $UX/DESIGNER.md; echo; echo '---'; cat $UX/CONTEXT.md; echo; echo '---'; cat $UX/SCREENS.md; echo; echo '---'; cat $UX/brief.md$EXTRA_CAT$INCLUDE_LINE; echo; echo 'Write the mockup to /ux-out/$safe/mockup.html (the directory exists; use that absolute path).')\" </dev/null" \
>"$outdir/proposal.md" 2>"$outdir/proposal.err"; then
echo "exit non-zero or timeout at $(date -Is); opencode exit codes are unreliable, judge by proposal.md" >>"$outdir/_lane.log"
fi
if [[ -s "$outdir/proposal.md" ]] && grep -q '^COMPLETE' "$outdir/proposal.md"; then
echo " ok [$model] $(wc -l <"$outdir/proposal.md") lines; mockup: $([[ -f "$outdir/mockup.html" ]] && echo yes || echo NO)"
else
echo " SUSPECT [$model] empty or no COMPLETE line; see $outdir/proposal.err"
fi
}
pids=()
for m in $MODELS; do run_lane "$m" & pids+=("$!"); done
echo "==> ${#pids[@]} lanes running (pids ${pids[*]}); one provider per lane"
wait "${pids[@]}" || true
# The container runs as root; hand the files back to the invoking user.
compose run --rm --no-deps -T -v "$RUN_DIR:/ux-out" auditor chown -R "$(id -u):$(id -g)" /ux-out >/dev/null 2>&1 || true
echo "finished $(date -Is)" >> "$RUN_DIR/manifest.txt"
echo "Proposals under: $RUN_DIR"